From d34cbbf7938457cdeb01c8be1beb604408031d9a Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 13:42:37 -0700 Subject: [PATCH 01/34] Create codeflash_wrap_decorator.py --- .../code_utils/codeflash_wrap_decorator.py | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 codeflash/code_utils/codeflash_wrap_decorator.py diff --git a/codeflash/code_utils/codeflash_wrap_decorator.py b/codeflash/code_utils/codeflash_wrap_decorator.py new file mode 100644 index 000000000..5dda746de --- /dev/null +++ b/codeflash/code_utils/codeflash_wrap_decorator.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import asyncio +import gc +import os +import sqlite3 +from enum import Enum +from functools import wraps +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Any, Callable, TypeVar + +import dill as pickle + + +class VerificationType(str, Enum): # moved from codeflash/verification/codeflash_capture.py + FUNCTION_CALL = ( + "function_call" # Correctness verification for a test function, checks input values and output values) + ) + INIT_STATE_FTO = "init_state_fto" # Correctness verification for fto class instance attributes after init + INIT_STATE_HELPER = "init_state_helper" # Correctness verification for helper class instance attributes after init + + +F = TypeVar("F", bound=Callable[..., Any]) + + +def get_run_tmp_file(file_path: Path) -> Path: # moved from codeflash/code_utils/code_utils.py + if not hasattr(get_run_tmp_file, "tmpdir"): + get_run_tmp_file.tmpdir = TemporaryDirectory(prefix="codeflash_") + return Path(get_run_tmp_file.tmpdir.name) / file_path + + +def extract_test_context_from_env() -> tuple[str, str | None, str]: + test_module = os.environ["CODEFLASH_TEST_MODULE"] + test_class = os.environ.get("CODEFLASH_TEST_CLASS", None) + test_function = os.environ["CODEFLASH_TEST_FUNCTION"] + + if test_module and test_function: + return (test_module, test_class if test_class else None, test_function) + + raise RuntimeError( + "Test context environment variables not set - ensure tests are run through codeflash test runner" + ) + + +def codeflash_behavior_async(func: F) -> F: + @wraps(func) + async def async_wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 + loop = asyncio.get_running_loop() + function_name = func.__name__ + line_id = os.environ["CODEFLASH_CURRENT_LINE_ID"] + loop_index = int(os.environ["CODEFLASH_LOOP_INDEX"]) + test_module_name, test_class_name, test_name = extract_test_context_from_env() + + test_id = f"{test_module_name}:{test_class_name}:{test_name}:{line_id}:{loop_index}" + + if not hasattr(async_wrapper, "index"): + async_wrapper.index = {} + if test_id in async_wrapper.index: + async_wrapper.index[test_id] += 1 + else: + async_wrapper.index[test_id] = 0 + + codeflash_test_index = async_wrapper.index[test_id] + invocation_id = f"{line_id}_{codeflash_test_index}" + test_stdout_tag = f"{test_module_name}:{(test_class_name + '.' if test_class_name else '')}{test_name}:{function_name}:{loop_index}:{invocation_id}" + + print(f"!$######{test_stdout_tag}######$!") + + iteration = os.environ.get("CODEFLASH_TEST_ITERATION", "0") + db_path = get_run_tmp_file(Path(f"test_return_values_{iteration}.sqlite")) + codeflash_con = sqlite3.connect(db_path) + codeflash_cur = codeflash_con.cursor() + + codeflash_cur.execute( + "CREATE TABLE IF NOT EXISTS test_results (test_module_path TEXT, test_class_name TEXT, " + "test_function_name TEXT, function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + "runtime INTEGER, return_value BLOB, verification_type TEXT)" + ) + + exception = None + counter = loop.time() + gc.disable() + try: + ret = func(*args, **kwargs) # coroutine creation has some overhead, though it is very small + counter = loop.time() + return_value = await ret # let's measure the actual execution time of the code + codeflash_duration = int((loop.time() - counter) * 1_000_000_000) + except Exception as e: + codeflash_duration = int((loop.time() - counter) * 1_000_000_000) + exception = e + finally: + gc.enable() + + print(f"!######{test_stdout_tag}######!") + + pickled_return_value = pickle.dumps(exception) if exception else pickle.dumps((args, kwargs, return_value)) + codeflash_cur.execute( + "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + test_module_name, + test_class_name, + test_name, + function_name, + loop_index, + invocation_id, + codeflash_duration, + pickled_return_value, + VerificationType.FUNCTION_CALL.value, + ), + ) + codeflash_con.commit() + codeflash_con.close() + + if exception: + raise exception + return return_value + + return async_wrapper + + +def codeflash_performance_async(func: F) -> F: + @wraps(func) + async def async_wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 + loop = asyncio.get_running_loop() + function_name = func.__name__ + line_id = os.environ["CODEFLASH_CURRENT_LINE_ID"] + loop_index = int(os.environ["CODEFLASH_LOOP_INDEX"]) + + test_module_name, test_class_name, test_name = extract_test_context_from_env() + + test_id = f"{test_module_name}:{test_class_name}:{test_name}:{line_id}:{loop_index}" + + if not hasattr(async_wrapper, "index"): + async_wrapper.index = {} + if test_id in async_wrapper.index: + async_wrapper.index[test_id] += 1 + else: + async_wrapper.index[test_id] = 0 + + codeflash_test_index = async_wrapper.index[test_id] + invocation_id = f"{line_id}_{codeflash_test_index}" + test_stdout_tag = f"{test_module_name}:{(test_class_name + '.' if test_class_name else '')}{test_name}:{function_name}:{loop_index}:{invocation_id}" + + print(f"!$######{test_stdout_tag}######$!") + + exception = None + counter = loop.time() + gc.disable() + try: + ret = func(*args, **kwargs) + counter = loop.time() + return_value = await ret + codeflash_duration = int((loop.time() - counter) * 1_000_000_000) + except Exception as e: + codeflash_duration = int((loop.time() - counter) * 1_000_000_000) + exception = e + finally: + gc.enable() + + print(f"!######{test_stdout_tag}:{codeflash_duration}######!") + + if exception: + raise exception + return return_value + + return async_wrapper From fa705e1b5e02642bb8af76fbaab98d76b601e782 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 13:53:15 -0700 Subject: [PATCH 02/34] first pass --- code_to_optimize/async_bubble_sort.py | 43 + .../code_directories/async_e2e/main.py | 16 + .../code_directories/async_e2e/pyproject.toml | 6 + .../async_e2e/tests/__init__.py | 0 codeflash/discovery/functions_to_optimize.py | 38 +- tests/scripts/end_to_end_test_async.py | 27 + tests/test_async_function_discovery.py | 286 +++++ tests/test_async_run_and_parse_tests.py | 1039 +++++++++++++++++ tests/test_async_wrapper_sqlite_validation.py | 285 +++++ tests/test_instrument_async_tests.py | 793 +++++++++++++ 10 files changed, 2530 insertions(+), 3 deletions(-) create mode 100644 code_to_optimize/async_bubble_sort.py create mode 100644 code_to_optimize/code_directories/async_e2e/main.py create mode 100644 code_to_optimize/code_directories/async_e2e/pyproject.toml create mode 100644 code_to_optimize/code_directories/async_e2e/tests/__init__.py create mode 100644 tests/scripts/end_to_end_test_async.py create mode 100644 tests/test_async_function_discovery.py create mode 100644 tests/test_async_run_and_parse_tests.py create mode 100644 tests/test_async_wrapper_sqlite_validation.py create mode 100644 tests/test_instrument_async_tests.py diff --git a/code_to_optimize/async_bubble_sort.py b/code_to_optimize/async_bubble_sort.py new file mode 100644 index 000000000..b87455299 --- /dev/null +++ b/code_to_optimize/async_bubble_sort.py @@ -0,0 +1,43 @@ +import asyncio +from typing import List, Union + + +async def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]: + """ + Async bubble sort implementation for testing. + """ + print("codeflash stdout: Async sorting list") + + await asyncio.sleep(0.01) + + n = len(lst) + for i in range(n): + for j in range(0, n - i - 1): + if lst[j] > lst[j + 1]: + lst[j], lst[j + 1] = lst[j + 1], lst[j] + + result = lst.copy() + print(f"result: {result}") + return result + + +class AsyncBubbleSorter: + """Class with async sorting method for testing.""" + + async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]: + """ + Async bubble sort implementation within a class. + """ + print("codeflash stdout: AsyncBubbleSorter.sorter() called") + + # Add some async delay + await asyncio.sleep(0.005) + + n = len(lst) + for i in range(n): + for j in range(0, n - i - 1): + if lst[j] > lst[j + 1]: + lst[j], lst[j + 1] = lst[j + 1], lst[j] + + result = lst.copy() + return result diff --git a/code_to_optimize/code_directories/async_e2e/main.py b/code_to_optimize/code_directories/async_e2e/main.py new file mode 100644 index 000000000..317068a1c --- /dev/null +++ b/code_to_optimize/code_directories/async_e2e/main.py @@ -0,0 +1,16 @@ +import time +import asyncio + + +async def retry_with_backoff(func, max_retries=3): + if max_retries < 1: + raise ValueError("max_retries must be at least 1") + last_exception = None + for attempt in range(max_retries): + try: + return await func() + except Exception as e: + last_exception = e + if attempt < max_retries - 1: + time.sleep(0.0001 * attempt) + raise last_exception diff --git a/code_to_optimize/code_directories/async_e2e/pyproject.toml b/code_to_optimize/code_directories/async_e2e/pyproject.toml new file mode 100644 index 000000000..d77155a9d --- /dev/null +++ b/code_to_optimize/code_directories/async_e2e/pyproject.toml @@ -0,0 +1,6 @@ +[tool.codeflash] +disable-telemetry = true +formatter-cmds = ["ruff check --exit-zero --fix $file", "ruff format $file"] +module-root = "." +test-framework = "pytest" +tests-root = "tests" \ No newline at end of file diff --git a/code_to_optimize/code_directories/async_e2e/tests/__init__.py b/code_to_optimize/code_directories/async_e2e/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/codeflash/discovery/functions_to_optimize.py b/codeflash/discovery/functions_to_optimize.py index bea01027b..dacbb1df6 100644 --- a/codeflash/discovery/functions_to_optimize.py +++ b/codeflash/discovery/functions_to_optimize.py @@ -86,6 +86,7 @@ def visit_FunctionDef(self, node: cst.FunctionDef) -> None: parents=list(reversed(ast_parents)), starting_line=pos.start.line, ending_line=pos.end.line, + is_async=bool(node.asynchronous), ) ) @@ -103,6 +104,15 @@ def visit_FunctionDef(self, node: FunctionDef) -> None: FunctionToOptimize(function_name=node.name, file_path=self.file_path, parents=self.ast_path[:]) ) + def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None: + # Check if the async function has a return statement and add it to the list + if function_has_return_statement(node) and not function_is_a_property(node): + self.functions.append( + FunctionToOptimize( + function_name=node.name, file_path=self.file_path, parents=self.ast_path[:], is_async=True + ) + ) + def generic_visit(self, node: ast.AST) -> None: if isinstance(node, (FunctionDef, AsyncFunctionDef, ClassDef)): self.ast_path.append(FunctionParent(node.name, node.__class__.__name__)) @@ -122,6 +132,7 @@ class FunctionToOptimize: parents: A list of parent scopes, which could be classes or functions. starting_line: The starting line number of the function in the file. ending_line: The ending line number of the function in the file. + is_async: Whether this function is defined as async. The qualified_name property provides the full name of the function, including any parent class or function names. The qualified_name_with_modules_from_root @@ -134,6 +145,7 @@ class FunctionToOptimize: parents: list[FunctionParent] # list[ClassDef | FunctionDef | AsyncFunctionDef] starting_line: Optional[int] = None ending_line: Optional[int] = None + is_async: bool = False @property def top_level_parent_name(self) -> str: @@ -147,7 +159,11 @@ def __str__(self) -> str: @property def qualified_name(self) -> str: - return self.function_name if self.parents == [] else f"{self.parents[0].name}.{self.function_name}" + if not self.parents: + return self.function_name + # Join all parent names with dots to handle nested classes properly + parent_path = ".".join(parent.name for parent in self.parents) + return f"{parent_path}.{self.function_name}" def qualified_name_with_modules_from_root(self, project_root_path: Path) -> str: return f"{module_name_from_file_path(self.file_path, project_root_path)}.{self.qualified_name}" @@ -411,11 +427,27 @@ def visit_FunctionDef(self, node: ast.FunctionDef) -> None: ) ) + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None: + if self.class_name is None and node.name == self.function_name: + self.is_top_level = True + self.function_has_args = any( + ( + bool(node.args.args), + bool(node.args.kwonlyargs), + bool(node.args.kwarg), + bool(node.args.posonlyargs), + bool(node.args.vararg), + ) + ) + def visit_ClassDef(self, node: ast.ClassDef) -> None: # iterate over the class methods if node.name == self.class_name: for body_node in node.body: - if isinstance(body_node, ast.FunctionDef) and body_node.name == self.function_name: + if ( + isinstance(body_node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and body_node.name == self.function_name + ): self.is_top_level = True if any( isinstance(decorator, ast.Name) and decorator.id == "classmethod" @@ -433,7 +465,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: # This way, if we don't have the class name, we can still find the static method for body_node in node.body: if ( - isinstance(body_node, ast.FunctionDef) + isinstance(body_node, (ast.FunctionDef, ast.AsyncFunctionDef)) and body_node.name == self.function_name and body_node.lineno in {self.line_no, self.line_no + 1} and any( diff --git a/tests/scripts/end_to_end_test_async.py b/tests/scripts/end_to_end_test_async.py new file mode 100644 index 000000000..5aed8f8ca --- /dev/null +++ b/tests/scripts/end_to_end_test_async.py @@ -0,0 +1,27 @@ +import os +import pathlib + +from end_to_end_test_utilities import CoverageExpectation, TestConfig, run_codeflash_command, run_with_retries + + +def run_test(expected_improvement_pct: int) -> bool: + config = TestConfig( + file_path="main.py", + expected_unit_tests=0, + min_improvement_x=0.1, + coverage_expectations=[ + CoverageExpectation( + function_name="retry_with_backoff", + expected_coverage=100.0, + expected_lines=[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], + ) + ], + ) + cwd = ( + pathlib.Path(__file__).parent.parent.parent / "code_to_optimize" / "code_directories" / "async_e2e" + ).resolve() + return run_codeflash_command(cwd, config, expected_improvement_pct) + + +if __name__ == "__main__": + exit(run_with_retries(run_test, int(os.getenv("EXPECTED_IMPROVEMENT_PCT", 10)))) \ No newline at end of file diff --git a/tests/test_async_function_discovery.py b/tests/test_async_function_discovery.py new file mode 100644 index 000000000..259d9ee24 --- /dev/null +++ b/tests/test_async_function_discovery.py @@ -0,0 +1,286 @@ +import tempfile +from pathlib import Path +import pytest + +from codeflash.discovery.functions_to_optimize import ( + find_all_functions_in_file, + get_functions_to_optimize, + inspect_top_level_functions_or_methods, +) +from codeflash.verification.verification_utils import TestConfig + + +@pytest.fixture +def temp_dir(): + with tempfile.TemporaryDirectory() as temp: + yield Path(temp) + + +def test_async_function_detection(temp_dir): + async_function = """ +async def async_function_with_return(): + await some_async_operation() + return 42 + +async def async_function_without_return(): + await some_async_operation() + print("No return") + +def regular_function(): + return 10 +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(async_function) + functions_found = find_all_functions_in_file(file_path) + + function_names = [fn.function_name for fn in functions_found[file_path]] + + assert "async_function_with_return" in function_names + assert "regular_function" in function_names + assert "async_function_without_return" not in function_names + + +def test_async_method_in_class(temp_dir): + code_with_async_method = """ +class AsyncClass: + async def async_method(self): + await self.do_something() + return "result" + + async def async_method_no_return(self): + await self.do_something() + pass + + def sync_method(self): + return "sync result" +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(code_with_async_method) + functions_found = find_all_functions_in_file(file_path) + + found_functions = functions_found[file_path] + function_names = [fn.function_name for fn in found_functions] + qualified_names = [fn.qualified_name for fn in found_functions] + + assert "async_method" in function_names + assert "AsyncClass.async_method" in qualified_names + + assert "sync_method" in function_names + assert "AsyncClass.sync_method" in qualified_names + + assert "async_method_no_return" not in function_names + + +def test_nested_async_functions(temp_dir): + nested_async = """ +async def outer_async(): + async def inner_async(): + return "inner" + + result = await inner_async() + return result + +def outer_sync(): + async def inner_async(): + return "inner from sync" + + return inner_async +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(nested_async) + functions_found = find_all_functions_in_file(file_path) + + function_names = [fn.function_name for fn in functions_found[file_path]] + + assert "outer_async" in function_names + assert "outer_sync" in function_names + assert "inner_async" not in function_names + + +def test_async_staticmethod_and_classmethod(temp_dir): + async_decorators = """ +class MyClass: + @staticmethod + async def async_static_method(): + await some_operation() + return "static result" + + @classmethod + async def async_class_method(cls): + await cls.some_operation() + return "class result" + + @property + async def async_property(self): + return await self.get_value() +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(async_decorators) + functions_found = find_all_functions_in_file(file_path) + + function_names = [fn.function_name for fn in functions_found[file_path]] + + assert "async_static_method" in function_names + assert "async_class_method" in function_names + + assert "async_property" not in function_names + + +def test_async_generator_functions(temp_dir): + async_generators = """ +async def async_generator_with_return(): + for i in range(10): + yield i + return "done" + +async def async_generator_no_return(): + for i in range(10): + yield i + +async def regular_async_with_return(): + result = await compute() + return result +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(async_generators) + functions_found = find_all_functions_in_file(file_path) + + function_names = [fn.function_name for fn in functions_found[file_path]] + + assert "async_generator_with_return" in function_names + assert "regular_async_with_return" in function_names + assert "async_generator_no_return" not in function_names + + +def test_inspect_async_top_level_functions(temp_dir): + code = """ +async def top_level_async(): + return 42 + +class AsyncContainer: + async def async_method(self): + async def nested_async(): + return 1 + return await nested_async() + + @staticmethod + async def async_static(): + return "static" + + @classmethod + async def async_classmethod(cls): + return "classmethod" +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(code) + + result = inspect_top_level_functions_or_methods(file_path, "top_level_async") + assert result.is_top_level + + result = inspect_top_level_functions_or_methods(file_path, "async_method", class_name="AsyncContainer") + assert result.is_top_level + + result = inspect_top_level_functions_or_methods(file_path, "nested_async", class_name="AsyncContainer") + assert not result.is_top_level + + result = inspect_top_level_functions_or_methods(file_path, "async_static", class_name="AsyncContainer") + assert result.is_top_level + assert result.is_staticmethod + + result = inspect_top_level_functions_or_methods(file_path, "async_classmethod", class_name="AsyncContainer") + assert result.is_top_level + assert result.is_classmethod + + +def test_get_functions_to_optimize_with_async(temp_dir): + mixed_code = """ +async def async_func_one(): + return await operation_one() + +def sync_func_one(): + return operation_one() + +async def async_func_two(): + print("no return") + +class MixedClass: + async def async_method(self): + return await self.operation() + + def sync_method(self): + return self.operation() +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(mixed_code) + + test_config = TestConfig( + tests_root="tests", + project_root_path=".", + test_framework="pytest", + tests_project_rootdir=Path() + ) + + functions, functions_count, _ = get_functions_to_optimize( + optimize_all=None, + replay_test=None, + file=file_path, + only_get_this_function=None, + test_cfg=test_config, + ignore_paths=[], + project_root=file_path.parent, + module_root=file_path.parent, + ) + + assert functions_count == 4 + + function_names = [fn.function_name for fn in functions[file_path]] + assert "async_func_one" in function_names + assert "sync_func_one" in function_names + assert "async_method" in function_names + assert "sync_method" in function_names + + assert "async_func_two" not in function_names + + +def test_async_function_parents(temp_dir): + complex_structure = """ +class OuterClass: + async def outer_method(self): + return 1 + + class InnerClass: + async def inner_method(self): + return 2 + +async def module_level_async(): + class LocalClass: + async def local_method(self): + return 3 + return LocalClass() +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(complex_structure) + functions_found = find_all_functions_in_file(file_path) + + found_functions = functions_found[file_path] + + for fn in found_functions: + if fn.function_name == "outer_method": + assert len(fn.parents) == 1 + assert fn.parents[0].name == "OuterClass" + assert fn.qualified_name == "OuterClass.outer_method" + elif fn.function_name == "inner_method": + assert len(fn.parents) == 2 + assert fn.parents[0].name == "OuterClass" + assert fn.parents[1].name == "InnerClass" + elif fn.function_name == "module_level_async": + assert len(fn.parents) == 0 + assert fn.qualified_name == "module_level_async" \ No newline at end of file diff --git a/tests/test_async_run_and_parse_tests.py b/tests/test_async_run_and_parse_tests.py new file mode 100644 index 000000000..b83be5c5a --- /dev/null +++ b/tests/test_async_run_and_parse_tests.py @@ -0,0 +1,1039 @@ +from __future__ import annotations + +import os +from argparse import Namespace +from pathlib import Path + +from codeflash.discovery.functions_to_optimize import FunctionToOptimize +from codeflash.models.models import CodePosition, FunctionParent, TestFile, TestFiles, TestingMode, TestType +from codeflash.optimization.optimizer import Optimizer +from codeflash.verification.instrument_codeflash_capture import instrument_codeflash_capture +from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators, inject_profiling_into_existing_test + +def test_async_bubble_sort_behavior_results() -> None: + test_code = """import asyncio +import pytest +from code_to_optimize.async_bubble_sort import async_sorter + + +@pytest.mark.asyncio +async def test_async_sort(): + input = [5, 4, 3, 2, 1, 0] + output = await async_sorter(input) + assert output == [0, 1, 2, 3, 4, 5] + + input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0] + output = await async_sorter(input) + assert output == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]""" + + test_path = ( + Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_bubble_sort_temp.py" + ).resolve() + test_path_perf = ( + Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_bubble_sort_perf_temp.py" + ).resolve() + fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() + original_code = fto_path.read_text("utf-8") + + try: + # Write test file + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + # Create async function to optimize + func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True) + + # For async functions, instrument the source module directly with decorators + source_success, instrumented_source = instrument_source_module_with_async_decorators( + fto_path, func, TestingMode.BEHAVIOR + ) + + assert source_success + assert instrumented_source is not None + assert '''import asyncio\nfrom typing import List, Union\n\nfrom codeflash.code_utils.codeflash_wrap_decorator import \\\n codeflash_behavior_async\n\n\n@codeflash_behavior_async\nasync def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation for testing.\n """\n print("codeflash stdout: Async sorting list")\n \n await asyncio.sleep(0.01)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n print(f"result: {result}")\n return result\n\n\nclass AsyncBubbleSorter:\n """Class with async sorting method for testing."""\n \n async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation within a class.\n """\n print("codeflash stdout: AsyncBubbleSorter.sorter() called")\n \n # Add some async delay\n await asyncio.sleep(0.005)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n return result\n''' in instrumented_source + + # Write the instrumented source back + fto_path.write_text(instrumented_source, "utf-8") + + # Add codeflash capture + instrument_codeflash_capture(func, {}, tests_root) + + # Create optimizer + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_async_bubble_sort_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_async_sort" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + # Create function optimizer and set up test files + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, coverage_data = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + assert test_results is not None + assert test_results.test_results is not None + + results_list = test_results.test_results + assert results_list[0].id.function_getting_tested == "async_sorter" + assert results_list[0].id.test_class_name is None + assert results_list[0].id.test_function_name == "test_async_sort" + assert results_list[0].did_pass + assert results_list[0].runtime is None or results_list[0].runtime >= 0 + + expected_stdout = "codeflash stdout: Async sorting list\nresult: [0, 1, 2, 3, 4, 5]\n" + assert expected_stdout == results_list[0].stdout + + + assert results_list[1].id.function_getting_tested == "async_sorter" + assert results_list[1].id.test_function_name == "test_async_sort" + assert results_list[1].did_pass + + expected_stdout2 = "codeflash stdout: Async sorting list\nresult: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n" + assert expected_stdout2 == results_list[1].stdout + + finally: + # Restore original code + fto_path.write_text(original_code, "utf-8") + # Clean up test files + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() + + +def test_async_class_method_behavior_results() -> None: + """Test async class method behavior with run_and_parse_tests.""" + test_code = """import asyncio +import pytest +from code_to_optimize.async_bubble_sort import AsyncBubbleSorter + + +@pytest.mark.asyncio +async def test_async_class_sort(): + sorter = AsyncBubbleSorter() + input = [3, 1, 4, 1, 5] + output = await sorter.sorter(input) + assert output == [1, 1, 3, 4, 5]""" + + test_path = ( + Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_class_bubble_sort_temp.py" + ).resolve() + test_path_perf = ( + Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_class_bubble_sort_perf_temp.py" + ).resolve() + fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() + original_code = fto_path.read_text("utf-8") + + try: + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + func = FunctionToOptimize( + function_name="sorter", + parents=[FunctionParent("AsyncBubbleSorter", "ClassDef")], + file_path=Path(fto_path), + is_async=True, + ) + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + fto_path, func, TestingMode.BEHAVIOR + ) + + assert source_success + assert instrumented_source is not None + assert "@codeflash_behavior_async" in instrumented_source + + fto_path.write_text(instrumented_source, "utf-8") + + instrument_codeflash_capture(func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_async_class_bubble_sort_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_async_class_sort" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, coverage_data = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + + assert test_results is not None + assert test_results.test_results is not None + + results_list = test_results.test_results + assert len(results_list) == 2, f"Expected 2 results but got {len(results_list)}: {[r.id.function_getting_tested for r in results_list]}" + + init_result = results_list[0] + sorter_result = results_list[1] + + + assert sorter_result.id.function_getting_tested == "sorter" + assert sorter_result.id.test_class_name is None + assert sorter_result.id.test_function_name == "test_async_class_sort" + assert sorter_result.did_pass + assert sorter_result.runtime is None or sorter_result.runtime >= 0 + + expected_stdout = "codeflash stdout: AsyncBubbleSorter.sorter() called\n" + assert expected_stdout == sorter_result.stdout + + assert ".__init__" in init_result.id.function_getting_tested + assert init_result.did_pass + + finally: + fto_path.write_text(original_code, "utf-8") + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() + + +def test_async_function_performance_mode() -> None: + test_code = """import asyncio +import pytest +from code_to_optimize.async_bubble_sort import async_sorter + + +@pytest.mark.asyncio +async def test_async_perf(): + input = [8, 7, 6, 5, 4, 3, 2, 1] + output = await async_sorter(input) + assert output == [1, 2, 3, 4, 5, 6, 7, 8]""" + + test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_perf_temp.py").resolve() + fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() + original_code = fto_path.read_text("utf-8") + + try: + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + # Create async function to optimize + func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True) + + # Instrument the source module with async performance decorators + source_success, instrumented_source = instrument_source_module_with_async_decorators( + fto_path, func, TestingMode.PERFORMANCE + ) + + assert source_success + assert instrumented_source is not None + assert '''import asyncio\nfrom typing import List, Union\n\nfrom codeflash.code_utils.codeflash_wrap_decorator import \\\n codeflash_performance_async\n\n\n@codeflash_performance_async\nasync def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation for testing.\n """\n print("codeflash stdout: Async sorting list")\n \n await asyncio.sleep(0.01)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n print(f"result: {result}")\n return result\n\n\nclass AsyncBubbleSorter:\n """Class with async sorting method for testing."""\n \n async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]:\n """\n Async bubble sort implementation within a class.\n """\n print("codeflash stdout: AsyncBubbleSorter.sorter() called")\n \n # Add some async delay\n await asyncio.sleep(0.005)\n \n n = len(lst)\n for i in range(n):\n for j in range(0, n - i - 1):\n if lst[j] > lst[j + 1]:\n lst[j], lst[j + 1] = lst[j + 1], lst[j]\n \n result = lst.copy()\n return result\n''' == instrumented_source + + fto_path.write_text(instrumented_source, "utf-8") + + instrument_codeflash_capture(func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_async_perf_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_async_perf" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path, # Same file for perf + ) + ] + ) + + test_results, coverage_data = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.PERFORMANCE, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + assert test_results is not None + assert test_results.test_results is not None + + finally: + # Restore original code + fto_path.write_text(original_code, "utf-8") + # Clean up test files + if test_path.exists(): + test_path.unlink() + + + +def test_async_function_error_handling() -> None: + test_code = """import asyncio +import pytest +from code_to_optimize.async_bubble_sort import async_error_function + + +@pytest.mark.asyncio +async def test_async_error(): + with pytest.raises(ValueError, match="Test error"): + await async_error_function([1, 2, 3])""" + + test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_error_temp.py").resolve() + test_path_perf = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_error_perf_temp.py").resolve() + fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() + original_code = fto_path.read_text("utf-8") + + try: + error_func_code = """ + +async def async_error_function(lst): + \"\"\"Async function that raises an error for testing.\"\"\" + await asyncio.sleep(0.001) # Small delay + raise ValueError("Test error") +""" + + modified_code = original_code + error_func_code + fto_path.write_text(modified_code, "utf-8") + + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + func = FunctionToOptimize(function_name="async_error_function", parents=[], file_path=Path(fto_path), is_async=True) + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + fto_path, func, TestingMode.BEHAVIOR + ) + + assert source_success + assert instrumented_source is not None + + expected_instrumented_source = """import asyncio +from typing import List, Union + +from codeflash.code_utils.codeflash_wrap_decorator import \\ + codeflash_behavior_async + + +async def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]: + \"\"\" + Async bubble sort implementation for testing. + \"\"\" + print("codeflash stdout: Async sorting list") + + await asyncio.sleep(0.01) + + n = len(lst) + for i in range(n): + for j in range(0, n - i - 1): + if lst[j] > lst[j + 1]: + lst[j], lst[j + 1] = lst[j + 1], lst[j] + + result = lst.copy() + print(f"result: {result}") + return result + + +class AsyncBubbleSorter: + \"\"\"Class with async sorting method for testing.\"\"\" + + async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]: + \"\"\" + Async bubble sort implementation within a class. + \"\"\" + print("codeflash stdout: AsyncBubbleSorter.sorter() called") + + # Add some async delay + await asyncio.sleep(0.005) + + n = len(lst) + for i in range(n): + for j in range(0, n - i - 1): + if lst[j] > lst[j + 1]: + lst[j], lst[j + 1] = lst[j + 1], lst[j] + + result = lst.copy() + return result + + +@codeflash_behavior_async +async def async_error_function(lst): + \"\"\"Async function that raises an error for testing.\"\"\" + await asyncio.sleep(0.001) # Small delay + raise ValueError("Test error") +""" + assert expected_instrumented_source == instrumented_source + + fto_path.write_text(instrumented_source, "utf-8") + instrument_codeflash_capture(func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_async_error_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_async_error" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, _ = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + assert test_results is not None + assert test_results.test_results is not None + assert len(test_results.test_results) >= 1 + + result = test_results.test_results[0] + assert result.id.function_getting_tested == "async_error_function" + assert result.did_pass + assert result.runtime is None or result.runtime >= 0 + + finally: + fto_path.write_text(original_code, "utf-8") + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() + + +def test_async_multiple_iterations() -> None: + test_code = """import asyncio +import pytest +from code_to_optimize.async_bubble_sort import async_sorter + + +@pytest.mark.asyncio +async def test_async_multi(): + input1 = [5, 4, 3] + output1 = await async_sorter(input1) + assert output1 == [3, 4, 5] + + input2 = [9, 7] + output2 = await async_sorter(input2) + assert output2 == [7, 9]""" + + test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_multi_temp.py").resolve() + test_path_perf = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_multi_perf_temp.py").resolve() + fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() + original_code = fto_path.read_text("utf-8") + + try: + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True) + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + fto_path, func, TestingMode.BEHAVIOR + ) + + assert source_success + fto_path.write_text(instrumented_source, "utf-8") + instrument_codeflash_capture(func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "3" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_async_multi_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_async_multi" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, _ = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=2, + pytest_max_loops=5, + testing_time=0.2, + ) + + assert test_results is not None + assert test_results.test_results is not None + assert len(test_results.test_results) >= 2 + + results_list = test_results.test_results + function_calls = [r for r in results_list if r.id.function_getting_tested == "async_sorter"] + assert len(function_calls) == 2 + + first_call = function_calls[0] + second_call = function_calls[1] + + assert first_call.stdout == "codeflash stdout: Async sorting list\nresult: [3, 4, 5]\n" + assert second_call.stdout == "codeflash stdout: Async sorting list\nresult: [7, 9]\n" + + assert first_call.did_pass + assert second_call.did_pass + assert first_call.runtime is None or first_call.runtime >= 0 + assert second_call.runtime is None or second_call.runtime >= 0 + + finally: + fto_path.write_text(original_code, "utf-8") + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() + + +def test_async_empty_input_edge_cases() -> None: + test_code = """import asyncio +import pytest +from code_to_optimize.async_bubble_sort import async_sorter + + +@pytest.mark.asyncio +async def test_async_edge_cases(): + # Empty list + empty = [] + result_empty = await async_sorter(empty) + assert result_empty == [] + + # Single item + single = [42] + result_single = await async_sorter(single) + assert result_single == [42] + + # Already sorted + sorted_list = [1, 2, 3, 4] + result_sorted = await async_sorter(sorted_list) + assert result_sorted == [1, 2, 3, 4]""" + + test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_edge_temp.py").resolve() + test_path_perf = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_async_edge_perf_temp.py").resolve() + fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/async_bubble_sort.py").resolve() + original_code = fto_path.read_text("utf-8") + + try: + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True) + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + fto_path, func, TestingMode.BEHAVIOR + ) + + assert source_success + fto_path.write_text(instrumented_source, "utf-8") + instrument_codeflash_capture(func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_async_edge_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_async_edge_cases" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, _ = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + assert test_results is not None + assert test_results.test_results is not None + assert len(test_results.test_results) >= 3 # 3 function calls for edge cases + + results_list = test_results.test_results + function_calls = [r for r in results_list if r.id.function_getting_tested == "async_sorter"] + assert len(function_calls) == 3 + + # Verify all calls passed + for call in function_calls: + assert call.did_pass + assert call.runtime is None or call.runtime >= 0 + + empty_call = function_calls[0] + single_call = function_calls[1] + sorted_call = function_calls[2] + + assert empty_call.stdout == "codeflash stdout: Async sorting list\nresult: []\n" + assert single_call.stdout == "codeflash stdout: Async sorting list\nresult: [42]\n" + assert sorted_call.stdout == "codeflash stdout: Async sorting list\nresult: [1, 2, 3, 4]\n" + + finally: + fto_path.write_text(original_code, "utf-8") + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() + + +def test_sync_function_behavior_in_async_test_environment() -> None: + sync_sorter_code = """def sync_sorter(lst): + \"\"\"Synchronous bubble sort for comparison.\"\"\" + print("codeflash stdout: Sync sorting list") + n = len(lst) + for i in range(n): + for j in range(0, n - i - 1): + if lst[j] > lst[j + 1]: + lst[j], lst[j + 1] = lst[j + 1], lst[j] + result = lst.copy() + print(f"result: {result}") + return result +""" + + test_code = """from code_to_optimize.sync_bubble_sort import sync_sorter + + +def test_sync_sort(): + input = [5, 4, 3, 2, 1, 0] + output = sync_sorter(input) + assert output == [0, 1, 2, 3, 4, 5] + + input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0] + output = sync_sorter(input) + assert output == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]""" + + test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_sync_in_async_temp.py").resolve() + test_path_perf = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_sync_in_async_perf_temp.py").resolve() + sync_fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/sync_bubble_sort.py").resolve() + + try: + with sync_fto_path.open("w") as f: + f.write(sync_sorter_code) + + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + func = FunctionToOptimize(function_name="sync_sorter", parents=[], file_path=Path(sync_fto_path), is_async=False) + + original_cwd = os.getcwd() + run_cwd = project_root_path + os.chdir(run_cwd) + + success, instrumented_test = inject_profiling_into_existing_test( + test_path, + [CodePosition(6, 13), CodePosition(10, 13)], # Lines where sync_sorter is called + func, + project_root_path, + "pytest", + mode=TestingMode.BEHAVIOR, + ) + os.chdir(original_cwd) + + assert success + assert instrumented_test is not None + + with test_path.open("w") as f: + f.write(instrumented_test) + + instrument_codeflash_capture(func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_sync_in_async_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_sync_sort" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, _ = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + assert test_results is not None + assert test_results.test_results is not None + + results_list = test_results.test_results + assert results_list[0].id.function_getting_tested == "sync_sorter" + assert results_list[0].id.iteration_id == "1_0" + assert results_list[0].id.test_class_name is None + assert results_list[0].id.test_function_name == "test_sync_sort" + assert results_list[0].did_pass + assert results_list[0].runtime > 0 + + expected_stdout = "codeflash stdout: Sync sorting list\nresult: [0, 1, 2, 3, 4, 5]\n" + assert expected_stdout == results_list[0].stdout + + if len(results_list) > 1: + assert results_list[1].id.function_getting_tested == "sync_sorter" + assert results_list[1].id.iteration_id == "4_0" + assert results_list[1].id.test_function_name == "test_sync_sort" + assert results_list[1].did_pass + + expected_stdout2 = "codeflash stdout: Sync sorting list\nresult: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n" + assert expected_stdout2 == results_list[1].stdout + + finally: + if sync_fto_path.exists(): + sync_fto_path.unlink() + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() + + +def test_mixed_async_sync_function_calls() -> None: + mixed_module_code = """import asyncio +from typing import List, Union + + +def sync_quick_sort(lst: List[Union[int, float]]) -> List[Union[int, float]]: + \"\"\"Synchronous quick sort.\"\"\" + print("codeflash stdout: Sync quick sort") + if len(lst) <= 1: + return lst.copy() + pivot = lst[len(lst) // 2] + left = [x for x in lst if x < pivot] + middle = [x for x in lst if x == pivot] + right = [x for x in lst if x > pivot] + result = sync_quick_sort(left) + middle + sync_quick_sort(right) + print(f"result: {result}") + return result + + +async def async_merge_sort(lst: List[Union[int, float]]) -> List[Union[int, float]]: + \"\"\"Asynchronous merge sort.\"\"\" + print("codeflash stdout: Async merge sort") + await asyncio.sleep(0.001) # Small delay + + if len(lst) <= 1: + return lst.copy() + + mid = len(lst) // 2 + left = await async_merge_sort(lst[:mid]) + right = await async_merge_sort(lst[mid:]) + + # Merge + result = [] + i = j = 0 + while i < len(left) and j < len(right): + if left[i] <= right[j]: + result.append(left[i]) + i += 1 + else: + result.append(right[j]) + j += 1 + result.extend(left[i:]) + result.extend(right[j:]) + + print(f"result: {result}") + return result + +""" + + test_code = """import asyncio +import pytest +from code_to_optimize.mixed_sort import sync_quick_sort, async_merge_sort + + +@pytest.mark.asyncio +async def test_mixed_sorting(): + # Test sync function + sync_input = [3, 1, 4, 1, 5] + sync_output = sync_quick_sort(sync_input) + assert sync_output == [1, 1, 3, 4, 5] + + # Test async function + async_input = [9, 2, 6, 5, 3] + async_output = await async_merge_sort(async_input) + assert async_output == [2, 3, 5, 6, 9]""" + + test_path = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_mixed_sort_temp.py").resolve() + test_path_perf = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/test_mixed_sort_perf_temp.py").resolve() + mixed_fto_path = (Path(__file__).parent.resolve() / "../code_to_optimize/mixed_sort.py").resolve() + + try: + with mixed_fto_path.open("w") as f: + f.write(mixed_module_code) + + with test_path.open("w") as f: + f.write(test_code) + + tests_root = (Path(__file__).parent.resolve() / "../code_to_optimize/tests/pytest/").resolve() + project_root_path = (Path(__file__).parent / "..").resolve() + + async_func = FunctionToOptimize(function_name="async_merge_sort", parents=[], file_path=Path(mixed_fto_path), is_async=True) + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + mixed_fto_path, async_func, TestingMode.BEHAVIOR + ) + + assert source_success + assert instrumented_source is not None + assert "@codeflash_behavior_async" in instrumented_source + assert "async def async_merge_sort" in instrumented_source + assert "def sync_quick_sort" in instrumented_source # Should preserve sync function + + mixed_fto_path.write_text(instrumented_source, "utf-8") + instrument_codeflash_capture(async_func, {}, tests_root) + + opt = Optimizer( + Namespace( + project_root=project_root_path, + disable_telemetry=True, + tests_root=tests_root, + test_framework="pytest", + pytest_cmd="pytest", + experiment_id=None, + test_project_root=project_root_path, + ) + ) + + test_env = os.environ.copy() + test_env["CODEFLASH_TEST_ITERATION"] = "0" + test_env["CODEFLASH_LOOP_INDEX"] = "1" + test_env["CODEFLASH_TEST_MODULE"] = "code_to_optimize.tests.pytest.test_mixed_sort_temp" + test_env["CODEFLASH_TEST_CLASS"] = "" + test_env["CODEFLASH_TEST_FUNCTION"] = "test_mixed_sorting" + test_env["CODEFLASH_CURRENT_LINE_ID"] = "0" + test_type = TestType.EXISTING_UNIT_TEST + + func_optimizer = opt.create_function_optimizer(async_func) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_path, + test_type=test_type, + original_file_path=test_path, + benchmarking_file_path=test_path_perf, + ) + ] + ) + + test_results, _ = func_optimizer.run_and_parse_tests( + testing_type=TestingMode.BEHAVIOR, + test_env=test_env, + test_files=func_optimizer.test_files, + optimization_iteration=0, + pytest_min_loops=1, + pytest_max_loops=1, + testing_time=0.1, + ) + + assert test_results is not None + assert test_results.test_results is not None + + results_list = test_results.test_results + async_calls = [r for r in results_list if r.id.function_getting_tested == "async_merge_sort"] + assert len(async_calls) >= 1 + + for call in async_calls: + assert call.did_pass + assert call.runtime is None or call.runtime >= 0 + assert "codeflash stdout: Async merge sort" in call.stdout + + finally: + if mixed_fto_path.exists(): + mixed_fto_path.unlink() + if test_path.exists(): + test_path.unlink() + if test_path_perf.exists(): + test_path_perf.unlink() \ No newline at end of file diff --git a/tests/test_async_wrapper_sqlite_validation.py b/tests/test_async_wrapper_sqlite_validation.py new file mode 100644 index 000000000..5cf7252f6 --- /dev/null +++ b/tests/test_async_wrapper_sqlite_validation.py @@ -0,0 +1,285 @@ +from __future__ import annotations + +import asyncio +import os +import sqlite3 +import tempfile +from pathlib import Path + +import pytest +import dill as pickle + +from codeflash.code_utils.codeflash_wrap_decorator import ( + codeflash_behavior_async, + codeflash_performance_async, +) +from codeflash.verification.codeflash_capture import VerificationType + + +class TestAsyncWrapperSQLiteValidation: + + @pytest.fixture + def test_env_setup(self, request): + original_env = {} + test_env = { + "CODEFLASH_LOOP_INDEX": "1", + "CODEFLASH_TEST_ITERATION": "0", + "CODEFLASH_TEST_MODULE": __name__, + "CODEFLASH_TEST_CLASS": "TestAsyncWrapperSQLiteValidation", + "CODEFLASH_TEST_FUNCTION": request.node.name, + "CODEFLASH_CURRENT_LINE_ID": "test_unit", + } + + for key, value in test_env.items(): + original_env[key] = os.environ.get(key) + os.environ[key] = value + + yield test_env + + for key, original_value in original_env.items(): + if original_value is None: + os.environ.pop(key, None) + else: + os.environ[key] = original_value + + @pytest.fixture + def temp_db_path(self, test_env_setup): + iteration = test_env_setup["CODEFLASH_TEST_ITERATION"] + from codeflash.code_utils.codeflash_wrap_decorator import get_run_tmp_file + db_path = get_run_tmp_file(Path(f"test_return_values_{iteration}.sqlite")) + + yield db_path + + if db_path.exists(): + db_path.unlink() + + @pytest.mark.asyncio + async def test_behavior_async_basic_function(self, test_env_setup, temp_db_path): + + @codeflash_behavior_async + async def simple_async_add(a: int, b: int) -> int: + await asyncio.sleep(0.001) + return a + b + + os.environ['CODEFLASH_CURRENT_LINE_ID'] = 'simple_async_add_59' + result = await simple_async_add(5, 3) + + assert result == 8 + + assert temp_db_path.exists() + + con = sqlite3.connect(temp_db_path) + cur = con.cursor() + + cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='test_results'") + assert cur.fetchone() is not None + + cur.execute("SELECT * FROM test_results") + rows = cur.fetchall() + + assert len(rows) == 1 + row = rows[0] + + (test_module_path, test_class_name, test_function_name, function_getting_tested, + loop_index, iteration_id, runtime, return_value_blob, verification_type) = row + + assert test_module_path == __name__ + assert test_class_name == "TestAsyncWrapperSQLiteValidation" + assert test_function_name == "test_behavior_async_basic_function" + assert function_getting_tested == "simple_async_add" + assert loop_index == 1 + # Line ID will be the actual line number from the source code, not a simple counter + assert iteration_id.startswith("simple_async_add_") and iteration_id.endswith("_0") + assert runtime > 0 + assert verification_type == VerificationType.FUNCTION_CALL.value + + unpickled_data = pickle.loads(return_value_blob) + args, kwargs, return_val = unpickled_data + + assert args == (5, 3) + assert kwargs == {} + assert return_val == 8 + + con.close() + + @pytest.mark.asyncio + async def test_behavior_async_exception_handling(self, test_env_setup, temp_db_path): + + @codeflash_behavior_async + async def async_divide(a: int, b: int) -> float: + await asyncio.sleep(0.001) + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + + result = await async_divide(10, 2) + assert result == 5.0 + + with pytest.raises(ValueError, match="Cannot divide by zero"): + await async_divide(10, 0) + + con = sqlite3.connect(temp_db_path) + cur = con.cursor() + cur.execute("SELECT * FROM test_results ORDER BY iteration_id") + rows = cur.fetchall() + + assert len(rows) == 2 + + success_row = rows[0] + success_data = pickle.loads(success_row[7]) # return_value_blob + args, kwargs, return_val = success_data + assert args == (10, 2) + assert return_val == 5.0 + + # Check exception record + exception_row = rows[1] + exception_data = pickle.loads(exception_row[7]) # return_value_blob + assert isinstance(exception_data, ValueError) + assert str(exception_data) == "Cannot divide by zero" + + con.close() + + @pytest.mark.asyncio + async def test_performance_async_no_database_storage(self, test_env_setup, temp_db_path, capsys): + """Test performance async decorator doesn't store to database.""" + + @codeflash_performance_async + async def async_multiply(a: int, b: int) -> int: + """Async function for performance testing.""" + await asyncio.sleep(0.002) + return a * b + + result = await async_multiply(4, 7) + + assert result == 28 + + assert not temp_db_path.exists() + + captured = capsys.readouterr() + output_lines = captured.out.strip().split('\n') + + assert len([line for line in output_lines if "!$######" in line]) == 1 + assert len([line for line in output_lines if "!######" in line and "######!" in line]) == 1 + + closing_tag = [line for line in output_lines if "!######" in line and "######!" in line][0] + assert "async_multiply" in closing_tag + + timing_part = closing_tag.split(":")[-1].replace("######!", "") + timing_value = int(timing_part) + assert timing_value > 0 # Should have positive timing + + @pytest.mark.asyncio + async def test_multiple_calls_indexing(self, test_env_setup, temp_db_path): + + @codeflash_behavior_async + async def async_increment(value: int) -> int: + await asyncio.sleep(0.001) + return value + 1 + + # Call the function multiple times + results = [] + for i in range(3): + result = await async_increment(i) + results.append(result) + + assert results == [1, 2, 3] + + con = sqlite3.connect(temp_db_path) + cur = con.cursor() + cur.execute("SELECT iteration_id, return_value FROM test_results ORDER BY iteration_id") + rows = cur.fetchall() + + assert len(rows) == 3 + + actual_ids = [row[0] for row in rows] + assert len(actual_ids) == 3 + + base_pattern = actual_ids[0].rsplit('_', 1)[0] # e.g., "async_increment_199" + expected_pattern = [f"{base_pattern}_{i}" for i in range(3)] + assert actual_ids == expected_pattern + + for i, (_, return_value_blob) in enumerate(rows): + args, kwargs, return_val = pickle.loads(return_value_blob) + assert args == (i,) + assert return_val == i + 1 + + con.close() + + @pytest.mark.asyncio + async def test_complex_async_function_with_kwargs(self, test_env_setup, temp_db_path): + + @codeflash_behavior_async + async def complex_async_func( + pos_arg: str, + *args: int, + keyword_arg: str = "default", + **kwargs: str + ) -> dict: + await asyncio.sleep(0.001) + return { + "pos_arg": pos_arg, + "args": args, + "keyword_arg": keyword_arg, + "kwargs": kwargs, + } + + result = await complex_async_func( + "hello", + 1, 2, 3, + keyword_arg="custom", + extra1="value1", + extra2="value2" + ) + + expected_result = { + "pos_arg": "hello", + "args": (1, 2, 3), + "keyword_arg": "custom", + "kwargs": {"extra1": "value1", "extra2": "value2"} + } + + assert result == expected_result + + con = sqlite3.connect(temp_db_path) + cur = con.cursor() + cur.execute("SELECT return_value FROM test_results") + row = cur.fetchone() + + stored_args, stored_kwargs, stored_result = pickle.loads(row[0]) + + assert stored_args == ("hello", 1, 2, 3) + assert stored_kwargs == {"keyword_arg": "custom", "extra1": "value1", "extra2": "value2"} + assert stored_result == expected_result + + con.close() + + @pytest.mark.asyncio + async def test_database_schema_validation(self, test_env_setup, temp_db_path): + + @codeflash_behavior_async + async def schema_test_func() -> str: + return "schema_test" + + await schema_test_func() + + con = sqlite3.connect(temp_db_path) + cur = con.cursor() + + cur.execute("PRAGMA table_info(test_results)") + columns = cur.fetchall() + + expected_columns = [ + (0, 'test_module_path', 'TEXT', 0, None, 0), + (1, 'test_class_name', 'TEXT', 0, None, 0), + (2, 'test_function_name', 'TEXT', 0, None, 0), + (3, 'function_getting_tested', 'TEXT', 0, None, 0), + (4, 'loop_index', 'INTEGER', 0, None, 0), + (5, 'iteration_id', 'TEXT', 0, None, 0), + (6, 'runtime', 'INTEGER', 0, None, 0), + (7, 'return_value', 'BLOB', 0, None, 0), + (8, 'verification_type', 'TEXT', 0, None, 0) + ] + + assert columns == expected_columns + con.close() + diff --git a/tests/test_instrument_async_tests.py b/tests/test_instrument_async_tests.py new file mode 100644 index 000000000..1149f42f2 --- /dev/null +++ b/tests/test_instrument_async_tests.py @@ -0,0 +1,793 @@ +import tempfile +from pathlib import Path +import uuid +import os + +import pytest + +from codeflash.code_utils.instrument_existing_tests import ( + add_async_decorator_to_function, + inject_profiling_into_existing_test, +) +from codeflash.discovery.functions_to_optimize import FunctionToOptimize +from codeflash.models.models import CodePosition, TestingMode + + +@pytest.fixture +def temp_dir(): + """Create a temporary directory for test files.""" + with tempfile.TemporaryDirectory() as temp: + yield Path(temp) + + +# @pytest.fixture +# def unique_test_iteration(): +# """Provide a unique test iteration ID and clean up database after test.""" +# # Generate unique iteration ID +# iteration_id = str(uuid.uuid4())[:8] + +# # Store original environment variable +# original_iteration = os.environ.get("CODEFLASH_TEST_ITERATION") + +# # Set unique iteration for this test +# os.environ["CODEFLASH_TEST_ITERATION"] = iteration_id + +# try: +# yield iteration_id +# finally: +# # Cleanup: restore original environment and delete database file +# if original_iteration is not None: +# os.environ["CODEFLASH_TEST_ITERATION"] = original_iteration +# elif "CODEFLASH_TEST_ITERATION" in os.environ: +# del os.environ["CODEFLASH_TEST_ITERATION"] + +# # Clean up database file +# try: +# from codeflash.code_utils.codeflash_wrap_decorator import get_run_tmp_file + +# db_path = get_run_tmp_file(Path(f"test_return_values_{iteration_id}.sqlite")) +# if db_path.exists(): +# db_path.unlink() +# except Exception: +# pass # Ignore cleanup errors + + +def test_async_decorator_application_behavior_mode(): + async_function_code = ''' +import asyncio + +async def async_function(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.01) + return x * y +''' + + expected_decorated_code = ''' +import asyncio + +from codeflash.code_utils.codeflash_wrap_decorator import \\ + codeflash_behavior_async + + +@codeflash_behavior_async +async def async_function(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.01) + return x * y +''' + + func = FunctionToOptimize( + function_name="async_function", file_path=Path("test_async.py"), parents=[], is_async=True + ) + + modified_code, decorator_added = add_async_decorator_to_function(async_function_code, func, TestingMode.BEHAVIOR) + + assert decorator_added + assert modified_code.strip() == expected_decorated_code.strip() + + +def test_async_decorator_application_performance_mode(): + async_function_code = ''' +import asyncio + +async def async_function(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.01) + return x * y +''' + + expected_decorated_code = ''' +import asyncio + +from codeflash.code_utils.codeflash_wrap_decorator import \\ + codeflash_performance_async + + +@codeflash_performance_async +async def async_function(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.01) + return x * y +''' + + func = FunctionToOptimize( + function_name="async_function", file_path=Path("test_async.py"), parents=[], is_async=True + ) + + modified_code, decorator_added = add_async_decorator_to_function(async_function_code, func, TestingMode.PERFORMANCE) + + assert decorator_added + assert modified_code.strip() == expected_decorated_code.strip() + + +def test_async_class_method_decorator_application(): + async_class_code = ''' +import asyncio + +class Calculator: + """Test class with async methods.""" + + async def async_method(self, a: int, b: int) -> int: + """Async method in class.""" + await asyncio.sleep(0.005) + return a ** b + + def sync_method(self, a: int, b: int) -> int: + """Sync method in class.""" + return a - b +''' + + expected_decorated_code = ''' +import asyncio + +from codeflash.code_utils.codeflash_wrap_decorator import \\ + codeflash_behavior_async + + +class Calculator: + """Test class with async methods.""" + + @codeflash_behavior_async + async def async_method(self, a: int, b: int) -> int: + """Async method in class.""" + await asyncio.sleep(0.005) + return a ** b + + def sync_method(self, a: int, b: int) -> int: + """Sync method in class.""" + return a - b +''' + + func = FunctionToOptimize( + function_name="async_method", + file_path=Path("test_async.py"), + parents=[{"name": "Calculator", "type": "ClassDef"}], + is_async=True, + ) + + modified_code, decorator_added = add_async_decorator_to_function(async_class_code, func, TestingMode.BEHAVIOR) + + assert decorator_added + assert modified_code.strip() == expected_decorated_code.strip() + + +def test_async_decorator_no_duplicate_application(): + already_decorated_code = ''' +from codeflash.code_utils.codeflash_wrap_decorator import codeflash_behavior_async +import asyncio + +@codeflash_behavior_async +async def async_function(x: int, y: int) -> int: + """Already decorated async function.""" + await asyncio.sleep(0.01) + return x * y +''' + + expected_reformatted_code = ''' +import asyncio + +from codeflash.code_utils.codeflash_wrap_decorator import \\ + codeflash_behavior_async + + +@codeflash_behavior_async +async def async_function(x: int, y: int) -> int: + """Already decorated async function.""" + await asyncio.sleep(0.01) + return x * y +''' + + func = FunctionToOptimize( + function_name="async_function", file_path=Path("test_async.py"), parents=[], is_async=True + ) + + modified_code, decorator_added = add_async_decorator_to_function(already_decorated_code, func, TestingMode.BEHAVIOR) + + assert not decorator_added + assert modified_code.strip() == expected_reformatted_code.strip() + + +def test_inject_profiling_async_function_behavior_mode(temp_dir): + source_module_code = ''' +import asyncio + +async def async_function(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.01) + return x * y +''' + + source_file = temp_dir / "my_module.py" + source_file.write_text(source_module_code) + + async_test_code = ''' +import asyncio +import pytest +from my_module import async_function + +@pytest.mark.asyncio +async def test_async_function(): + """Test async function behavior.""" + result = await async_function(5, 3) + assert result == 15 + + result2 = await async_function(2, 4) + assert result2 == 8 +''' + + test_file = temp_dir / "test_async.py" + test_file.write_text(async_test_code) + + func = FunctionToOptimize(function_name="async_function", parents=[], file_path=Path("my_module.py"), is_async=True) + + # First instrument the source module + from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + source_file, func, TestingMode.BEHAVIOR + ) + + assert source_success is True + assert instrumented_source is not None + assert "@codeflash_behavior_async" in instrumented_source + assert "from codeflash.code_utils.codeflash_wrap_decorator import" in instrumented_source + assert "codeflash_behavior_async" in instrumented_source + + source_file.write_text(instrumented_source) + + success, instrumented_test_code = inject_profiling_into_existing_test( + test_file, [CodePosition(8, 18), CodePosition(11, 19)], func, temp_dir, "pytest", mode=TestingMode.BEHAVIOR + ) + + # For async functions, once source is decorated, test injection should fail + # This is expected behavior - async instrumentation happens at the decorator level + assert success is False + assert instrumented_test_code is None + + +def test_inject_profiling_async_function_performance_mode(temp_dir): + source_module_code = ''' +import asyncio + +async def async_function(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.01) + return x * y +''' + + source_file = temp_dir / "my_module.py" + source_file.write_text(source_module_code) + + # Create the test file + async_test_code = ''' +import asyncio +import pytest +from my_module import async_function + +@pytest.mark.asyncio +async def test_async_function(): + """Test async function performance.""" + result = await async_function(5, 3) + assert result == 15 +''' + + test_file = temp_dir / "test_async.py" + test_file.write_text(async_test_code) + + func = FunctionToOptimize(function_name="async_function", parents=[], file_path=Path("my_module.py"), is_async=True) + + # First instrument the source module + from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + source_file, func, TestingMode.PERFORMANCE + ) + + assert source_success is True + assert instrumented_source is not None + assert "@codeflash_performance_async" in instrumented_source + # Check for the import with line continuation formatting + assert "from codeflash.code_utils.codeflash_wrap_decorator import" in instrumented_source + assert "codeflash_performance_async" in instrumented_source + + # Write the instrumented source back + source_file.write_text(instrumented_source) + + # Now test the full pipeline with source module path + success, instrumented_test_code = inject_profiling_into_existing_test( + test_file, [CodePosition(8, 18)], func, temp_dir, "pytest", mode=TestingMode.PERFORMANCE + ) + + # For async functions, once source is decorated, test injection should fail + # This is expected behavior - async instrumentation happens at the decorator level + assert success is False + assert instrumented_test_code is None + + +def test_mixed_sync_async_instrumentation(temp_dir): + source_module_code = ''' +import asyncio + +def sync_function(x: int, y: int) -> int: + """Regular sync function.""" + return x * y + +async def async_function(x: int, y: int) -> int: + """Simple async function.""" + await asyncio.sleep(0.01) + return x * y +''' + + source_file = temp_dir / "my_module.py" + source_file.write_text(source_module_code) + + mixed_test_code = ''' +import asyncio +import pytest +from my_module import sync_function, async_function + +@pytest.mark.asyncio +async def test_mixed_functions(): + """Test both sync and async functions.""" + sync_result = sync_function(10, 5) + assert sync_result == 50 + + async_result = await async_function(3, 4) + assert async_result == 12 +''' + + test_file = temp_dir / "test_mixed.py" + test_file.write_text(mixed_test_code) + + async_func = FunctionToOptimize( + function_name="async_function", parents=[], file_path=Path("my_module.py"), is_async=True + ) + + from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + source_file, async_func, TestingMode.BEHAVIOR + ) + + assert source_success + assert instrumented_source is not None + assert "@codeflash_behavior_async" in instrumented_source + assert "from codeflash.code_utils.codeflash_wrap_decorator import" in instrumented_source + assert "codeflash_behavior_async" in instrumented_source + # Sync function should remain unchanged + assert "def sync_function(x: int, y: int) -> int:" in instrumented_source + + # Write instrumented source back + source_file.write_text(instrumented_source) + + success, instrumented_test_code = inject_profiling_into_existing_test( + test_file, + [CodePosition(8, 18), CodePosition(11, 19)], + async_func, + temp_dir, + "pytest", + mode=TestingMode.BEHAVIOR, + ) + + # Async functions should not be instrumented at the test level + assert not success + assert instrumented_test_code is None + + +def test_async_function_qualified_name_handling(): + nested_async_code = ''' +import asyncio + +class OuterClass: + class InnerClass: + async def nested_async_method(self, x: int) -> int: + """Nested async method.""" + await asyncio.sleep(0.001) + return x * 2 +''' + + func = FunctionToOptimize( + function_name="nested_async_method", + file_path=Path("test_nested.py"), + parents=[{"name": "OuterClass", "type": "ClassDef"}, {"name": "InnerClass", "type": "ClassDef"}], + is_async=True, + ) + + modified_code, decorator_added = add_async_decorator_to_function(nested_async_code, func, TestingMode.BEHAVIOR) + + expected_output = ( + """import asyncio + +from codeflash.code_utils.codeflash_wrap_decorator import \\ + codeflash_behavior_async + + +class OuterClass: + class InnerClass: + @codeflash_behavior_async + async def nested_async_method(self, x: int) -> int: + \"\"\"Nested async method.\"\"\" + await asyncio.sleep(0.001) + return x * 2 +""" + ) + + assert modified_code.strip() == expected_output.strip() + + +def test_async_decorator_with_existing_decorators(): + """Test async decorator application when function already has other decorators.""" + decorated_async_code = ''' +import asyncio +from functools import wraps + +def my_decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + return await func(*args, **kwargs) + return wrapper + +@my_decorator +async def async_function(x: int, y: int) -> int: + """Async function with existing decorator.""" + await asyncio.sleep(0.01) + return x * y +''' + + func = FunctionToOptimize( + function_name="async_function", file_path=Path("test_async.py"), parents=[], is_async=True + ) + + modified_code, decorator_added = add_async_decorator_to_function(decorated_async_code, func, TestingMode.BEHAVIOR) + + assert decorator_added + # Should add codeflash decorator above existing decorators + assert "@codeflash_behavior_async" in modified_code + assert "@my_decorator" in modified_code + # Codeflash decorator should come first + codeflash_pos = modified_code.find("@codeflash_behavior_async") + my_decorator_pos = modified_code.find("@my_decorator") + assert codeflash_pos < my_decorator_pos + + +def test_sync_function_not_affected_by_async_logic(): + sync_function_code = ''' +def sync_function(x: int, y: int) -> int: + """Regular sync function.""" + return x + y +''' + + sync_func = FunctionToOptimize( + function_name="sync_function", + file_path=Path("test_sync.py"), + parents=[], + is_async=False, + ) + + modified_code, decorator_added = add_async_decorator_to_function( + sync_function_code, sync_func, TestingMode.BEHAVIOR + ) + + assert not decorator_added + assert modified_code == sync_function_code + +def test_inject_profiling_async_multiple_calls_same_test(temp_dir): + """Test that multiple async function calls within the same test function get correctly numbered 0, 1, 2, etc.""" + source_module_code = ''' +import asyncio + +async def async_sorter(items): + """Simple async sorter for testing.""" + await asyncio.sleep(0.001) + return sorted(items) +''' + + source_file = temp_dir / "async_sorter.py" + source_file.write_text(source_module_code) + + test_code_multiple_calls = """ +import asyncio +import pytest +from async_sorter import async_sorter + +@pytest.mark.asyncio +async def test_single_call(): + result = await async_sorter([42]) + assert result == [42] + +@pytest.mark.asyncio +async def test_multiple_calls(): + result1 = await async_sorter([3, 1, 2]) + result2 = await async_sorter([5, 4]) + result3 = await async_sorter([9, 8, 7, 6]) + assert result1 == [1, 2, 3] + assert result2 == [4, 5] + assert result3 == [6, 7, 8, 9] +""" + + test_file = temp_dir / "test_async_sorter.py" + test_file.write_text(test_code_multiple_calls) + + func = FunctionToOptimize( + function_name="async_sorter", parents=[], file_path=Path("async_sorter.py"), is_async=True + ) + + # First instrument the source module with async decorators + from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators + + source_success, instrumented_source = instrument_source_module_with_async_decorators( + source_file, func, TestingMode.BEHAVIOR + ) + + assert source_success + assert instrumented_source is not None + assert "@codeflash_behavior_async" in instrumented_source + + source_file.write_text(instrumented_source) + + import ast + + tree = ast.parse(test_code_multiple_calls) + call_positions = [] + for node in ast.walk(tree): + if isinstance(node, ast.Await) and isinstance(node.value, ast.Call): + if (hasattr(node.value.func, "id") and node.value.func.id == "async_sorter") or ( + hasattr(node.value.func, "attr") and node.value.func.attr == "async_sorter" + ): + call_positions.append(CodePosition(node.lineno, node.col_offset)) + + assert len(call_positions) == 4 + + success, instrumented_test_code = inject_profiling_into_existing_test( + test_file, call_positions, func, temp_dir, "pytest", mode=TestingMode.BEHAVIOR + ) + + assert success + assert instrumented_test_code is not None + + assert "os.environ['CODEFLASH_CURRENT_LINE_ID'] = '0'" in instrumented_test_code + + # Count occurrences of each line_id to verify numbering + line_id_0_count = instrumented_test_code.count("os.environ['CODEFLASH_CURRENT_LINE_ID'] = '0'") + line_id_1_count = instrumented_test_code.count("os.environ['CODEFLASH_CURRENT_LINE_ID'] = '1'") + line_id_2_count = instrumented_test_code.count("os.environ['CODEFLASH_CURRENT_LINE_ID'] = '2'") + + + assert line_id_0_count == 2, f"Expected 2 occurrences of line_id '0', got {line_id_0_count}" + assert line_id_1_count == 1, f"Expected 1 occurrence of line_id '1', got {line_id_1_count}" + assert line_id_2_count == 1, f"Expected 1 occurrence of line_id '2', got {line_id_2_count}" + + + +def test_async_behavior_decorator_return_values_and_test_ids(): + """Test that async behavior decorator correctly captures return values, test IDs, and stores data in database.""" + import asyncio + import os + import sqlite3 + from pathlib import Path + + import dill as pickle + + from codeflash.code_utils.codeflash_wrap_decorator import codeflash_behavior_async + + @codeflash_behavior_async + async def test_async_multiply(x: int, y: int) -> int: + """Simple async function for testing.""" + await asyncio.sleep(0.001) # Small delay to simulate async work + return x * y + + test_env = { + "CODEFLASH_TEST_MODULE": "test_module", + "CODEFLASH_TEST_CLASS": None, + "CODEFLASH_TEST_FUNCTION": "test_async_multiply_function", + "CODEFLASH_CURRENT_LINE_ID": "0", + "CODEFLASH_LOOP_INDEX": "1", + "CODEFLASH_TEST_ITERATION": "2", + } + + original_env = {k: os.environ.get(k) for k in test_env} + for k, v in test_env.items(): + if v is not None: + os.environ[k] = v + elif k in os.environ: + del os.environ[k] + + try: + result = asyncio.run(test_async_multiply(6, 7)) + + assert result == 42, f"Expected return value 42, got {result}" + + from codeflash.code_utils.codeflash_wrap_decorator import get_run_tmp_file + + db_path = get_run_tmp_file(Path(f"test_return_values_2.sqlite")) + + # Verify database exists and has data + assert db_path.exists(), f"Database file not created at {db_path}" + + # Read and verify database contents + con = sqlite3.connect(db_path) + cur = con.cursor() + + cur.execute("SELECT * FROM test_results") + rows = cur.fetchall() + + assert len(rows) == 1, f"Expected 1 database row, got {len(rows)}" + + row = rows[0] + ( + test_module, + test_class, + test_function, + function_name, + loop_index, + iteration_id, + runtime, + return_value_blob, + verification_type, + ) = row + + assert test_module == "test_module", f"Expected test_module 'test_module', got '{test_module}'" + assert test_class is None, f"Expected test_class None, got '{test_class}'" + assert test_function == "test_async_multiply_function", ( + f"Expected test_function 'test_async_multiply_function', got '{test_function}'" + ) + assert function_name == "test_async_multiply", ( + f"Expected function_name 'test_async_multiply', got '{function_name}'" + ) + assert loop_index == 1, f"Expected loop_index 1, got {loop_index}" + assert iteration_id == "0_0", f"Expected iteration_id '0_0', got '{iteration_id}'" + assert verification_type == "function_call", ( + f"Expected verification_type 'function_call', got '{verification_type}'" + ) + unpickled_data = pickle.loads(return_value_blob) + args, kwargs, actual_return_value = unpickled_data + + assert args == (6, 7), f"Expected args (6, 7), got {args}" + assert kwargs == {}, f"Expected empty kwargs, got {kwargs}" + + assert actual_return_value == 42, f"Expected stored return value 42, got {actual_return_value}" + + con.close() + + finally: + for k, v in original_env.items(): + if v is not None: + os.environ[k] = v + elif k in os.environ: + del os.environ[k] + + +def test_async_decorator_comprehensive_return_values_and_test_ids(): + import asyncio + import os + import sqlite3 + from pathlib import Path + + import dill as pickle + + from codeflash.code_utils.codeflash_wrap_decorator import codeflash_behavior_async, get_run_tmp_file + + @codeflash_behavior_async + async def async_multiply_add(x: int, y: int, z: int = 1) -> int: + """Async function that multiplies x*y then adds z.""" + await asyncio.sleep(0.001) + result = (x * y) + z + return result + + test_env = { + "CODEFLASH_TEST_MODULE": "test_comprehensive_module", + "CODEFLASH_TEST_CLASS": "AsyncTestClass", + "CODEFLASH_TEST_FUNCTION": "test_comprehensive_async_function", + "CODEFLASH_CURRENT_LINE_ID": "3", + "CODEFLASH_LOOP_INDEX": "2", + "CODEFLASH_TEST_ITERATION": "3", + } + + original_env = {k: os.environ.get(k) for k in test_env} + for k, v in test_env.items(): + if v is not None: + os.environ[k] = v + elif k in os.environ: + del os.environ[k] + + try: + test_cases = [ + {"args": (5, 3), "kwargs": {}, "expected": 16}, # (5 * 3) + 1 = 16 + {"args": (2, 4), "kwargs": {"z": 10}, "expected": 18}, # (2 * 4) + 10 = 18 + {"args": (7, 6), "kwargs": {}, "expected": 43}, # (7 * 6) + 1 = 43 + ] + + results = [] + for test_case in test_cases: + result = asyncio.run(async_multiply_add(*test_case["args"], **test_case["kwargs"])) + results.append(result) + + # Verify each return value is exactly correct + assert result == test_case["expected"], ( + f"Expected {test_case['expected']}, got {result} for args {test_case['args']}, kwargs {test_case['kwargs']}" + ) + + db_path = get_run_tmp_file(Path(f"test_return_values_3.sqlite")) + assert db_path.exists(), f"Database not created at {db_path}" + + con = sqlite3.connect(db_path) + cur = con.cursor() + + cur.execute( + "SELECT test_module_path, test_class_name, test_function_name, function_getting_tested, loop_index, iteration_id, runtime, return_value, verification_type FROM test_results ORDER BY rowid" + ) + rows = cur.fetchall() + + assert len(rows) == 3, f"Expected 3 database rows, got {len(rows)}" + + for i, ( + test_module, + test_class, + test_function, + function_name, + loop_index, + iteration_id, + runtime, + return_value_blob, + verification_type, + ) in enumerate(rows): + assert test_module == "test_comprehensive_module", ( + f"Row {i}: Expected test_module 'test_comprehensive_module', got '{test_module}'" + ) + assert test_class == "AsyncTestClass", f"Row {i}: Expected test_class 'AsyncTestClass', got '{test_class}'" + assert test_function == "test_comprehensive_async_function", ( + f"Row {i}: Expected test_function 'test_comprehensive_async_function', got '{test_function}'" + ) + assert function_name == "async_multiply_add", ( + f"Row {i}: Expected function_name 'async_multiply_add', got '{function_name}'" + ) + assert loop_index == 2, f"Row {i}: Expected loop_index 2, got {loop_index}" + assert verification_type == "function_call", ( + f"Row {i}: Expected verification_type 'function_call', got '{verification_type}'" + ) + + expected_iteration_id = f"3_{i}" + assert iteration_id == expected_iteration_id, ( + f"Row {i}: Expected iteration_id '{expected_iteration_id}', got '{iteration_id}'" + ) + + + args, kwargs, actual_return_value = pickle.loads(return_value_blob) + expected_args = test_cases[i]["args"] + expected_kwargs = test_cases[i]["kwargs"] + expected_return = test_cases[i]["expected"] + + assert args == expected_args, f"Row {i}: Expected args {expected_args}, got {args}" + assert kwargs == expected_kwargs, f"Row {i}: Expected kwargs {expected_kwargs}, got {kwargs}" + assert actual_return_value == expected_return, ( + f"Row {i}: Expected return value {expected_return}, got {actual_return_value}" + ) + + con.close() + + finally: + for k, v in original_env.items(): + if v is not None: + os.environ[k] = v + elif k in os.environ: + del os.environ[k] From 3e964746f527c7b4c9f2c6f2336b59dc1694c4e0 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 14:12:48 -0700 Subject: [PATCH 03/34] Add async function support to AI service and optimization pipeline - Add is_async parameter to AiServiceClient.optimize_python_code method - Pass is_async flag to both optimization and test generation endpoints - Update optimization pipeline to pass is_async from FunctionToOptimize - Remove async function blocking check in function optimizer - Remove unused has_any_async_functions import This enables async functions to be properly processed by the optimization pipeline and sent to the AI service with the correct async context. --- codeflash/api/aiservice.py | 4 ++++ codeflash/optimization/function_optimizer.py | 8 ++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codeflash/api/aiservice.py b/codeflash/api/aiservice.py index 62e1df174..de3e2928a 100644 --- a/codeflash/api/aiservice.py +++ b/codeflash/api/aiservice.py @@ -102,6 +102,8 @@ def optimize_python_code( # noqa: D417 trace_id: str, num_candidates: int = 10, experiment_metadata: ExperimentMetadata | None = None, + *, + is_async: bool = False, ) -> list[OptimizedCandidate]: """Optimize the given python code for performance by making a request to the Django endpoint. @@ -133,6 +135,7 @@ def optimize_python_code( # noqa: D417 "repo_owner": git_repo_owner, "repo_name": git_repo_name, "n_candidates": N_CANDIDATES_EFFECTIVE, + "is_async": is_async, } logger.info("!lsp|Generating optimized candidates…") @@ -488,6 +491,7 @@ def generate_regression_tests( # noqa: D417 "test_index": test_index, "python_version": platform.python_version(), "codeflash_version": codeflash_version, + "is_async": function_to_optimize.is_async, } try: response = self.make_ai_service_request("/testgen", payload=payload, timeout=600) diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index 926adff95..ccb7913eb 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -36,7 +36,6 @@ diff_length, file_name_from_test_module_name, get_run_tmp_file, - has_any_async_functions, module_name_from_file_path, restore_conftest, unified_diff_strings, @@ -259,11 +258,6 @@ def can_be_optimized(self) -> Result[tuple[bool, CodeOptimizationContext, dict[P helper_code = f.read() original_helper_code[helper_function_path] = helper_code - async_code = any( - has_any_async_functions(code_string.code) for code_string in code_context.read_writable_code.code_strings - ) - if async_code: - return Failure("Codeflash does not support async functions in the code to optimize.") # Random here means that we still attempt optimization with a fractional chance to see if # last time we could not find an optimization, maybe this time we do. # Random is before as a performance optimization, swapping the two 'and' statements has the same effect @@ -1080,6 +1074,7 @@ def generate_tests_and_optimizations( self.function_trace_id[:-4] + "EXP0" if run_experiment else self.function_trace_id, n_candidates, ExperimentMetadata(id=self.experiment_id, group="control") if run_experiment else None, + is_async=self.function_to_optimize.is_async, ) future_candidates_exp = None @@ -1095,6 +1090,7 @@ def generate_tests_and_optimizations( self.function_trace_id[:-4] + "EXP1", n_candidates, ExperimentMetadata(id=self.experiment_id, group="experiment"), + is_async=self.function_to_optimize.is_async, ) futures.append(future_candidates_exp) From 759fdb1612ae5f65d6c36a1d5e00d3425d2a585b Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 14:24:21 -0700 Subject: [PATCH 04/34] Complete async test instrumentation and utilities implementation - Added comprehensive async test instrumentation (AsyncCallInstrumenter class) - Implemented async decorator functions (add_async_decorator_to_function, instrument_source_module_with_async_decorators) - Added async wrapper decorators (codeflash_behavior_async, codeflash_performance_async) - Updated edit_generated_tests.py to handle AsyncFunctionDef nodes in test parsing - Updated coverage_utils.py to include async functions in coverage analysis --- codeflash/code_utils/coverage_utils.py | 4 +- codeflash/code_utils/edit_generated_tests.py | 20 +- .../code_utils/instrument_existing_tests.py | 372 +++++++++++++++++- 3 files changed, 388 insertions(+), 8 deletions(-) diff --git a/codeflash/code_utils/coverage_utils.py b/codeflash/code_utils/coverage_utils.py index 966910630..11df7687e 100644 --- a/codeflash/code_utils/coverage_utils.py +++ b/codeflash/code_utils/coverage_utils.py @@ -14,7 +14,9 @@ def extract_dependent_function(main_function: str, code_context: CodeOptimizatio """Extract the single dependent function from the code context excluding the main function.""" ast_tree = ast.parse(code_context.testgen_context_code) - dependent_functions = {node.name for node in ast_tree.body if isinstance(node, ast.FunctionDef)} + dependent_functions = { + node.name for node in ast_tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + } if main_function in dependent_functions: dependent_functions.discard(main_function) diff --git a/codeflash/code_utils/edit_generated_tests.py b/codeflash/code_utils/edit_generated_tests.py index 09c1c163c..8e50b1d71 100644 --- a/codeflash/code_utils/edit_generated_tests.py +++ b/codeflash/code_utils/edit_generated_tests.py @@ -32,9 +32,11 @@ def __init__( def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: self.context_stack.append(node.name) - for inner_node in ast.walk(node): + for inner_node in node.body: if isinstance(inner_node, ast.FunctionDef): self.visit_FunctionDef(inner_node) + elif isinstance(inner_node, ast.AsyncFunctionDef): + self.visit_AsyncFunctionDef(inner_node) self.context_stack.pop() return node @@ -50,6 +52,14 @@ def get_comment(self, match_key: str) -> str: return f"# {format_time(original_time)} -> {format_time(optimized_time)} ({perf_gain}% {status})" def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: + self._process_function_def_common(node) + return node + + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AsyncFunctionDef: + self._process_function_def_common(node) + return node + + def _process_function_def_common(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None: self.context_stack.append(node.name) i = len(node.body) - 1 test_qualified_name = ".".join(self.context_stack) @@ -60,8 +70,9 @@ def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: j = len(line_node.body) - 1 while j >= 0: compound_line_node: ast.stmt = line_node.body[j] - internal_node: ast.AST - for internal_node in ast.walk(compound_line_node): + nodes_to_check = [compound_line_node] + nodes_to_check.extend(getattr(compound_line_node, "body", [])) + for internal_node in nodes_to_check: if isinstance(internal_node, (ast.stmt, ast.Assign)): inv_id = str(i) + "_" + str(j) match_key = key + "#" + inv_id @@ -75,7 +86,6 @@ def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: self.results[line_node.lineno] = self.get_comment(match_key) i -= 1 self.context_stack.pop() - return node def get_fn_call_linenos( @@ -201,7 +211,7 @@ def remove_functions_from_generated_tests( for generated_test in generated_tests.generated_tests: for test_function in test_functions_to_remove: function_pattern = re.compile( - rf"(@pytest\.mark\.parametrize\(.*?\)\s*)?def\s+{re.escape(test_function)}\(.*?\):.*?(?=\ndef\s|$)", + rf"(@pytest\.mark\.parametrize\(.*?\)\s*)?(async\s+)?def\s+{re.escape(test_function)}\(.*?\):.*?(?=\n(async\s+)?def\s|$)", re.DOTALL, ) diff --git a/codeflash/code_utils/instrument_existing_tests.py b/codeflash/code_utils/instrument_existing_tests.py index 94e732eb3..db08f8afc 100644 --- a/codeflash/code_utils/instrument_existing_tests.py +++ b/codeflash/code_utils/instrument_existing_tests.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING import isort +import libcst as cst from codeflash.cli_cmds.console import logger from codeflash.code_utils.code_utils import get_run_tmp_file, module_name_from_file_path @@ -76,6 +77,10 @@ def find_and_update_line_node( call_node = node if isinstance(node.func, ast.Name): function_name = node.func.id + + if self.function_object.is_async: + return [test_node] + node.func = ast.Name(id="codeflash_wrap", ctx=ast.Load()) node.args = [ ast.Name(id=function_name, ctx=ast.Load()), @@ -97,6 +102,9 @@ def find_and_update_line_node( if isinstance(node.func, ast.Attribute): function_to_test = node.func.attr if function_to_test == self.function_object.function_name: + if self.function_object.is_async: + return [test_node] + function_name = ast.unparse(node.func) node.func = ast.Name(id="codeflash_wrap", ctx=ast.Load()) node.args = [ @@ -283,6 +291,139 @@ def visit_FunctionDef(self, node: ast.FunctionDef, test_class_name: str | None = return node +class AsyncCallInstrumenter(ast.NodeTransformer): + def __init__( + self, + function: FunctionToOptimize, + module_path: str, + test_framework: str, + call_positions: list[CodePosition], + mode: TestingMode = TestingMode.BEHAVIOR, + ) -> None: + self.mode = mode + self.function_object = function + self.class_name = None + self.only_function_name = function.function_name + self.module_path = module_path + self.test_framework = test_framework + self.call_positions = call_positions + self.did_instrument = False + # Track function call count per test function + self.async_call_counter: dict[str, int] = {} + if len(function.parents) == 1 and function.parents[0].type == "ClassDef": + self.class_name = function.top_level_parent_name + + def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: + # Add timeout decorator for unittest test classes if needed + if self.test_framework == "unittest": + timeout_decorator = ast.Call( + func=ast.Name(id="timeout_decorator.timeout", ctx=ast.Load()), + args=[ast.Constant(value=15)], + keywords=[], + ) + for item in node.body: + if ( + isinstance(item, ast.FunctionDef) + and item.name.startswith("test_") + and not any( + isinstance(d, ast.Call) + and isinstance(d.func, ast.Name) + and d.func.id == "timeout_decorator.timeout" + for d in item.decorator_list + ) + ): + item.decorator_list.append(timeout_decorator) + return self.generic_visit(node) + + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AsyncFunctionDef: + if not node.name.startswith("test_"): + return node + + return self._process_test_function(node) + + def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: + # Only process test functions + if not node.name.startswith("test_"): + return node + + return self._process_test_function(node) + + def _process_test_function( + self, node: ast.AsyncFunctionDef | ast.FunctionDef + ) -> ast.AsyncFunctionDef | ast.FunctionDef: + if self.test_framework == "unittest" and not any( + isinstance(d, ast.Call) and isinstance(d.func, ast.Name) and d.func.id == "timeout_decorator.timeout" + for d in node.decorator_list + ): + timeout_decorator = ast.Call( + func=ast.Name(id="timeout_decorator.timeout", ctx=ast.Load()), + args=[ast.Constant(value=15)], + keywords=[], + ) + node.decorator_list.append(timeout_decorator) + + # Initialize counter for this test function + if node.name not in self.async_call_counter: + self.async_call_counter[node.name] = 0 + + new_body = [] + + for _i, stmt in enumerate(node.body): + transformed_stmt, added_env_assignment = self._instrument_statement(stmt, node.name) + + if added_env_assignment: + current_call_index = self.async_call_counter[node.name] + self.async_call_counter[node.name] += 1 + + env_assignment = ast.Assign( + targets=[ + ast.Subscript( + value=ast.Attribute( + value=ast.Name(id="os", ctx=ast.Load()), attr="environ", ctx=ast.Load() + ), + slice=ast.Constant(value="CODEFLASH_CURRENT_LINE_ID"), + ctx=ast.Store(), + ) + ], + value=ast.Constant(value=f"{current_call_index}"), + lineno=stmt.lineno if hasattr(stmt, "lineno") else 1, + ) + new_body.append(env_assignment) + self.did_instrument = True + + new_body.append(transformed_stmt) + + node.body = new_body + return node + + def _instrument_statement(self, stmt: ast.stmt, _node_name: str) -> tuple[ast.stmt, bool]: + for node in ast.walk(stmt): + if ( + isinstance(node, ast.Await) + and isinstance(node.value, ast.Call) + and self._is_target_call(node.value) + and self._call_in_positions(node.value) + ): + # Check if this call is in one of our target positions + return stmt, True # Return original statement but signal we added env var + + return stmt, False + + def _is_target_call(self, call_node: ast.Call) -> bool: + """Check if this call node is calling our target async function.""" + if isinstance(call_node.func, ast.Name): + return call_node.func.id == self.function_object.function_name + if isinstance(call_node.func, ast.Attribute): + return call_node.func.attr == self.function_object.function_name + return False + + def _call_in_positions(self, call_node: ast.Call) -> bool: + if not hasattr(call_node, "lineno") or not hasattr(call_node, "col_offset"): + return False + + return node_in_call_position(call_node, self.call_positions) + + class FunctionImportedAsVisitor(ast.NodeVisitor): """Checks if a function has been imported as an alias. We only care about the alias then. @@ -310,6 +451,7 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None: file_path=self.function.file_path, starting_line=self.function.starting_line, ending_line=self.function.ending_line, + is_async=self.function.is_async, ) else: self.imported_as = FunctionToOptimize( @@ -318,10 +460,32 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None: file_path=self.function.file_path, starting_line=self.function.starting_line, ending_line=self.function.ending_line, + is_async=self.function.is_async, ) -def inject_profiling_into_existing_test( +def instrument_source_module_with_async_decorators( + source_path: Path, function_to_optimize: FunctionToOptimize, mode: TestingMode = TestingMode.BEHAVIOR +) -> tuple[bool, str | None]: + if not function_to_optimize.is_async: + return False, None + + try: + with source_path.open(encoding="utf8") as f: + source_code = f.read() + + modified_code, decorator_added = add_async_decorator_to_function(source_code, function_to_optimize, mode) + + if decorator_added: + return True, modified_code + + except Exception: + return False, None + else: + return False, None + + +def inject_async_profiling_into_existing_test( test_path: Path, call_positions: list[CodePosition], function_to_optimize: FunctionToOptimize, @@ -329,8 +493,10 @@ def inject_profiling_into_existing_test( test_framework: str, mode: TestingMode = TestingMode.BEHAVIOR, ) -> tuple[bool, str | None]: + """Inject profiling for async function calls by setting environment variables before each call.""" with test_path.open(encoding="utf8") as f: test_code = f.read() + try: tree = ast.parse(test_code) except SyntaxError: @@ -342,6 +508,47 @@ def inject_profiling_into_existing_test( import_visitor.visit(tree) func = import_visitor.imported_as + async_instrumenter = AsyncCallInstrumenter(func, test_module_path, test_framework, call_positions, mode=mode) + tree = async_instrumenter.visit(tree) + + if not async_instrumenter.did_instrument: + return False, None + + # Add necessary imports + new_imports = [ast.Import(names=[ast.alias(name="os")])] + if test_framework == "unittest": + new_imports.append(ast.Import(names=[ast.alias(name="timeout_decorator")])) + + tree.body = [*new_imports, *tree.body] + return True, isort.code(ast.unparse(tree), float_to_top=True) + + +def inject_profiling_into_existing_test( + test_path: Path, + call_positions: list[CodePosition], + function_to_optimize: FunctionToOptimize, + tests_project_root: Path, + test_framework: str, + mode: TestingMode = TestingMode.BEHAVIOR, +) -> tuple[bool, str | None]: + if function_to_optimize.is_async: + return inject_async_profiling_into_existing_test( + test_path, call_positions, function_to_optimize, tests_project_root, test_framework, mode + ) + + with test_path.open(encoding="utf8") as f: + test_code = f.read() + try: + tree = ast.parse(test_code) + except SyntaxError: + logger.exception(f"Syntax error in code in file - {test_path}") + return False, None + + test_module_path = module_name_from_file_path(test_path, tests_project_root) + import_visitor = FunctionImportedAsVisitor(function_to_optimize) + import_visitor.visit(tree) + func = import_visitor.imported_as + tree = InjectPerfOnly(func, test_module_path, test_framework, call_positions, mode=mode).visit(tree) new_imports = [ ast.Import(names=[ast.alias(name="time")]), @@ -354,7 +561,9 @@ def inject_profiling_into_existing_test( ) if test_framework == "unittest": new_imports.append(ast.Import(names=[ast.alias(name="timeout_decorator")])) - tree.body = [*new_imports, create_wrapper_function(mode), *tree.body] + additional_functions = [create_wrapper_function(mode)] + + tree.body = [*new_imports, *additional_functions, *tree.body] return True, isort.code(ast.unparse(tree), float_to_top=True) @@ -735,3 +944,162 @@ def create_wrapper_function(mode: TestingMode = TestingMode.BEHAVIOR) -> ast.Fun decorator_list=[], returns=None, ) + + +class AsyncDecoratorAdder(cst.CSTTransformer): + """Transformer that adds async decorator to async function definitions.""" + + def __init__(self, function: FunctionToOptimize, mode: TestingMode = TestingMode.BEHAVIOR) -> None: + """Initialize the transformer. + + Args: + ---- + function: The FunctionToOptimize object representing the target async function. + mode: The testing mode to determine which decorator to apply. + + """ + super().__init__() + self.function = function + self.mode = mode + self.qualified_name_parts = function.qualified_name.split(".") + self.context_stack = [] + self.added_decorator = False + + # Choose decorator based on mode + self.decorator_name = ( + "codeflash_behavior_async" if mode == TestingMode.BEHAVIOR else "codeflash_performance_async" + ) + + def visit_ClassDef(self, node: cst.ClassDef) -> None: + # Track when we enter a class + self.context_stack.append(node.name.value) + + def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.ClassDef: # noqa: ARG002 + # Pop the context when we leave a class + self.context_stack.pop() + return updated_node + + def visit_FunctionDef(self, node: cst.FunctionDef) -> None: + # Track when we enter a function + self.context_stack.append(node.name.value) + + def leave_FunctionDef(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef) -> cst.FunctionDef: + # Check if this is an async function and matches our target + if original_node.asynchronous is not None and self.context_stack == self.qualified_name_parts: + # Check if the decorator is already present + has_decorator = any( + self._is_target_decorator(decorator.decorator) for decorator in original_node.decorators + ) + + # Only add the decorator if it's not already there + if not has_decorator: + new_decorator = cst.Decorator(decorator=cst.Name(value=self.decorator_name)) + + # Add our new decorator to the existing decorators + updated_decorators = [new_decorator, *list(updated_node.decorators)] + updated_node = updated_node.with_changes(decorators=tuple(updated_decorators)) + self.added_decorator = True + + # Pop the context when we leave a function + self.context_stack.pop() + return updated_node + + def _is_target_decorator(self, decorator_node: cst.Name | cst.Attribute | cst.Call) -> bool: + """Check if a decorator matches our target decorator name.""" + if isinstance(decorator_node, cst.Name): + return decorator_node.value in { + "codeflash_trace_async", + "codeflash_behavior_async", + "codeflash_performance_async", + } + if isinstance(decorator_node, cst.Call) and isinstance(decorator_node.func, cst.Name): + return decorator_node.func.value in { + "codeflash_trace_async", + "codeflash_behavior_async", + "codeflash_performance_async", + } + return False + + +class AsyncDecoratorImportAdder(cst.CSTTransformer): + """Transformer that adds the import for async decorators.""" + + def __init__(self, mode: TestingMode = TestingMode.BEHAVIOR) -> None: + self.mode = mode + self.has_import = False + + def visit_ImportFrom(self, node: cst.ImportFrom) -> None: + # Check if the async decorator import is already present + if ( + isinstance(node.module, cst.Attribute) + and isinstance(node.module.value, cst.Attribute) + and isinstance(node.module.value.value, cst.Name) + and node.module.value.value.value == "codeflash" + and node.module.value.attr.value == "code_utils" + and node.module.attr.value == "codeflash_wrap_decorator" + and not isinstance(node.names, cst.ImportStar) + ): + decorator_name = ( + "codeflash_behavior_async" if self.mode == TestingMode.BEHAVIOR else "codeflash_performance_async" + ) + for import_alias in node.names: + if import_alias.name.value == decorator_name: + self.has_import = True + + def leave_Module(self, original_node: cst.Module, updated_node: cst.Module) -> cst.Module: # noqa: ARG002 + # If the import is already there, don't add it again + if self.has_import: + return updated_node + + # Choose import based on mode + decorator_name = ( + "codeflash_behavior_async" if self.mode == TestingMode.BEHAVIOR else "codeflash_performance_async" + ) + + # Parse the import statement into a CST node + import_node = cst.parse_statement(f"from codeflash.code_utils.codeflash_wrap_decorator import {decorator_name}") + + # Add the import to the module's body + return updated_node.with_changes(body=[import_node, *list(updated_node.body)]) + + +def add_async_decorator_to_function( + source_code: str, function: FunctionToOptimize, mode: TestingMode = TestingMode.BEHAVIOR +) -> tuple[str, bool]: + """Add async decorator to an async function definition. + + Args: + ---- + source_code: The source code to modify. + function: The FunctionToOptimize object representing the target async function. + mode: The testing mode to determine which decorator to apply. + + Returns: + ------- + Tuple of (modified_source_code, was_decorator_added). + + """ + if not function.is_async: + return source_code, False + + try: + module = cst.parse_module(source_code) + + # Add the decorator to the function + decorator_transformer = AsyncDecoratorAdder(function, mode) + module = module.visit(decorator_transformer) + + # Add the import if decorator was added + if decorator_transformer.added_decorator: + import_transformer = AsyncDecoratorImportAdder(mode) + module = module.visit(import_transformer) + + return isort.code(module.code, float_to_top=True), decorator_transformer.added_decorator + except Exception as e: + logger.exception(f"Error adding async decorator to function {function.qualified_name}: {e}") + return source_code, False + + +def create_instrumented_source_module_path(source_path: Path, temp_dir: Path) -> Path: + instrumented_filename = f"instrumented_{source_path.name}" + return temp_dir / instrumented_filename From 669e22ab026eb98e74fefbf06eb0f4d20f1da533 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 14:32:37 -0700 Subject: [PATCH 05/34] Complete async throughput measurement support - Add async throughput fields to Explanation dataclass - Implement throughput-based performance improvement calculation - Add MIN_THROUGHPUT_IMPROVEMENT_THRESHOLD configuration constant - Update explanation logic to prefer throughput metrics for async functions - Restore LSP compatibility with conditional test result display --- codeflash/code_utils/config_consts.py | 1 + codeflash/result/critic.py | 61 +++++++++++++++++++++++---- codeflash/result/explanation.py | 50 ++++++++++++++++++++-- 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/codeflash/code_utils/config_consts.py b/codeflash/code_utils/config_consts.py index eab4f0fd1..cc1eb50da 100644 --- a/codeflash/code_utils/config_consts.py +++ b/codeflash/code_utils/config_consts.py @@ -3,6 +3,7 @@ MAX_FUNCTION_TEST_SECONDS = 60 N_CANDIDATES = 5 MIN_IMPROVEMENT_THRESHOLD = 0.05 +MIN_THROUGHPUT_IMPROVEMENT_THRESHOLD = 0.10 # 10% minimum improvement for async throughput MAX_TEST_FUNCTION_RUNS = 50 MAX_CUMULATIVE_TEST_RUNTIME_NANOSECONDS = 100e6 # 100ms N_TESTS_TO_GENERATE = 2 diff --git a/codeflash/result/critic.py b/codeflash/result/critic.py index 33d872807..d0ff62176 100644 --- a/codeflash/result/critic.py +++ b/codeflash/result/critic.py @@ -8,8 +8,9 @@ COVERAGE_THRESHOLD, MIN_IMPROVEMENT_THRESHOLD, MIN_TESTCASE_PASSED_THRESHOLD, + MIN_THROUGHPUT_IMPROVEMENT_THRESHOLD, ) -from codeflash.models.test_type import TestType +from codeflash.models.models import TestType if TYPE_CHECKING: from codeflash.models.models import CoverageData, OptimizedCandidateResult, OriginalCodeBaseline @@ -25,20 +26,41 @@ def performance_gain(*, original_runtime_ns: int, optimized_runtime_ns: int) -> return (original_runtime_ns - optimized_runtime_ns) / optimized_runtime_ns +def throughput_gain(*, original_throughput: int, optimized_throughput: int) -> float: + """Calculate the throughput gain of an optimized code over the original code. + + This value multiplied by 100 gives the percentage improvement in throughput. + For throughput, higher values are better (more executions per time period). + """ + if original_throughput == 0: + return 0.0 + return (optimized_throughput - original_throughput) / original_throughput + + def speedup_critic( candidate_result: OptimizedCandidateResult, original_code_runtime: int, best_runtime_until_now: int | None, *, disable_gh_action_noise: bool = False, + original_async_throughput: int | None = None, + best_throughput_until_now: int | None = None, ) -> bool: """Take in a correct optimized Test Result and decide if the optimization should actually be surfaced to the user. - Ensure that the optimization is actually faster than the original code, above the noise floor. - The noise floor is a function of the original code runtime. Currently, the noise floor is 2xMIN_IMPROVEMENT_THRESHOLD - when the original runtime is less than 10 microseconds, and becomes MIN_IMPROVEMENT_THRESHOLD for any higher runtime. - The noise floor is doubled when benchmarking on a (noisy) GitHub Action virtual instance, also we want to be more confident there. + Evaluates both runtime performance and async throughput improvements. + + For runtime performance: + - Ensures the optimization is actually faster than the original code, above the noise floor. + - The noise floor is a function of the original code runtime. Currently, the noise floor is 2xMIN_IMPROVEMENT_THRESHOLD + when the original runtime is less than 10 microseconds, and becomes MIN_IMPROVEMENT_THRESHOLD for any higher runtime. + - The noise floor is doubled when benchmarking on a (noisy) GitHub Action virtual instance. + + For async throughput (when available): + - Evaluates throughput improvements using MIN_THROUGHPUT_IMPROVEMENT_THRESHOLD + - Throughput improvements complement runtime improvements for async functions """ + # Runtime performance evaluation noise_floor = 3 * MIN_IMPROVEMENT_THRESHOLD if original_code_runtime < 10000 else MIN_IMPROVEMENT_THRESHOLD if not disable_gh_action_noise and env_utils.is_ci(): noise_floor = noise_floor * 2 # Increase the noise floor in GitHub Actions mode @@ -46,10 +68,31 @@ def speedup_critic( perf_gain = performance_gain( original_runtime_ns=original_code_runtime, optimized_runtime_ns=candidate_result.best_test_runtime ) - if best_runtime_until_now is None: - # collect all optimizations with this - return bool(perf_gain > noise_floor) - return bool(perf_gain > noise_floor and candidate_result.best_test_runtime < best_runtime_until_now) + runtime_improved = perf_gain > noise_floor + + # Check runtime comparison with best so far + runtime_is_best = best_runtime_until_now is None or candidate_result.best_test_runtime < best_runtime_until_now + + throughput_improved = True # Default to True if no throughput data + throughput_is_best = True # Default to True if no throughput data + + if original_async_throughput is not None and candidate_result.async_throughput is not None: + if original_async_throughput > 0: + throughput_gain_value = throughput_gain( + original_throughput=original_async_throughput, optimized_throughput=candidate_result.async_throughput + ) + throughput_improved = throughput_gain_value > MIN_THROUGHPUT_IMPROVEMENT_THRESHOLD + + throughput_is_best = ( + best_throughput_until_now is None or candidate_result.async_throughput > best_throughput_until_now + ) + + if original_async_throughput is not None and candidate_result.async_throughput is not None: + # When throughput data is available, accept if EITHER throughput OR runtime improves significantly + throughput_acceptance = throughput_improved and throughput_is_best + runtime_acceptance = runtime_improved and runtime_is_best + return throughput_acceptance or runtime_acceptance + return runtime_improved and runtime_is_best def quantity_of_tests_critic(candidate_result: OptimizedCandidateResult | OriginalCodeBaseline) -> bool: diff --git a/codeflash/result/explanation.py b/codeflash/result/explanation.py index 5d1c2a270..959cdabd6 100644 --- a/codeflash/result/explanation.py +++ b/codeflash/result/explanation.py @@ -12,6 +12,7 @@ from codeflash.code_utils.time_utils import humanize_runtime from codeflash.lsp.helpers import is_LSP_enabled from codeflash.models.models import BenchmarkDetail, TestResults +from codeflash.result.critic import performance_gain, throughput_gain @dataclass(frozen=True, config={"arbitrary_types_allowed": True}) @@ -24,9 +25,28 @@ class Explanation: function_name: str file_path: Path benchmark_details: Optional[list[BenchmarkDetail]] = None + original_async_throughput: Optional[int] = None + best_async_throughput: Optional[int] = None @property def perf_improvement_line(self) -> str: + runtime_improvement = self.speedup + + if ( + self.original_async_throughput is not None + and self.best_async_throughput is not None + and self.original_async_throughput > 0 + ): + throughput_improvement = throughput_gain( + original_throughput=self.original_async_throughput, optimized_throughput=self.best_async_throughput + ) + + # Use throughput metrics if throughput improvement is better or runtime got worse + if throughput_improvement > runtime_improvement or runtime_improvement <= 0: + throughput_pct = f"{throughput_improvement * 100:,.0f}%" + throughput_x = f"{throughput_improvement + 1:,.2f}x" + return f"{throughput_pct} improvement ({throughput_x} faster)." + return f"{self.speedup_pct} improvement ({self.speedup_x} faster)." @property @@ -46,6 +66,23 @@ def __str__(self) -> str: # TODO: Sometimes the explanation says something similar to "This is the code that was optimized", remove such parts original_runtime_human = humanize_runtime(self.original_runtime_ns) best_runtime_human = humanize_runtime(self.best_runtime_ns) + + # Determine if we're showing throughput or runtime improvements + runtime_improvement = self.speedup + is_using_throughput_metric = False + + if ( + self.original_async_throughput is not None + and self.best_async_throughput is not None + and self.original_async_throughput > 0 + ): + throughput_improvement = throughput_gain( + original_throughput=self.original_async_throughput, optimized_throughput=self.best_async_throughput + ) + + if throughput_improvement > runtime_improvement or runtime_improvement <= 0: + is_using_throughput_metric = True + benchmark_info = "" if self.benchmark_details: @@ -86,13 +123,18 @@ def __str__(self) -> str: console.print(table) benchmark_info = cast("StringIO", console.file).getvalue() + "\n" # Cast for mypy - test_report = self.winning_behavior_test_results.get_test_pass_fail_report_by_type() - test_report_str = TestResults.report_to_string(test_report) + if is_using_throughput_metric: + performance_description = ( + f"Throughput improved from {self.original_async_throughput} to {self.best_async_throughput} operations/second " + f"(runtime: {original_runtime_human} → {best_runtime_human})\n\n" + ) + else: + performance_description = f"Runtime went down from {original_runtime_human} to {best_runtime_human} \n\n" return ( f"Optimized {self.function_name} in {self.file_path}\n" f"{self.perf_improvement_line}\n" - f"Runtime went down from {original_runtime_human} to {best_runtime_human} \n\n" + + performance_description + (benchmark_info if benchmark_info else "") + self.raw_explanation_message + " \n\n" @@ -101,7 +143,7 @@ def __str__(self) -> str: "" if is_LSP_enabled() else "The new optimized code was tested for correctness. The results are listed below.\n" - + test_report_str + + f"{TestResults.report_to_string(self.winning_behavior_test_results.get_test_pass_fail_report_by_type())}\n" ) ) From 05e5a94233a3ed44e83c5d7cbba5aaadc5b92328 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 14:37:44 -0700 Subject: [PATCH 06/34] formatting --- codeflash/result/explanation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeflash/result/explanation.py b/codeflash/result/explanation.py index 959cdabd6..7b28f8ac1 100644 --- a/codeflash/result/explanation.py +++ b/codeflash/result/explanation.py @@ -12,7 +12,7 @@ from codeflash.code_utils.time_utils import humanize_runtime from codeflash.lsp.helpers import is_LSP_enabled from codeflash.models.models import BenchmarkDetail, TestResults -from codeflash.result.critic import performance_gain, throughput_gain +from codeflash.result.critic import throughput_gain @dataclass(frozen=True, config={"arbitrary_types_allowed": True}) @@ -143,7 +143,7 @@ def __str__(self) -> str: "" if is_LSP_enabled() else "The new optimized code was tested for correctness. The results are listed below.\n" - + f"{TestResults.report_to_string(self.winning_behavior_test_results.get_test_pass_fail_report_by_type())}\n" + f"{TestResults.report_to_string(self.winning_behavior_test_results.get_test_pass_fail_report_by_type())}\n" ) ) From 4325416827e5f38f71e5557f6f783e635c83bf49 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 14:49:29 -0700 Subject: [PATCH 07/34] resolve paths in my case, i've started using symlinks a bit more often, and the current impl causes issues, we need to resolve the symlinked path too. --- codeflash/code_utils/code_utils.py | 2 +- codeflash/context/code_context_extractor.py | 4 ++-- tests/test_code_context_extractor.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codeflash/code_utils/code_utils.py b/codeflash/code_utils/code_utils.py index 4ff010046..4dc02eb82 100644 --- a/codeflash/code_utils/code_utils.py +++ b/codeflash/code_utils/code_utils.py @@ -173,7 +173,7 @@ def get_qualified_name(module_name: str, full_qualified_name: str) -> str: def module_name_from_file_path(file_path: Path, project_root_path: Path, *, traverse_up: bool = False) -> str: try: - relative_path = file_path.relative_to(project_root_path) + relative_path = file_path.resolve().relative_to(project_root_path.resolve()) return relative_path.with_suffix("").as_posix().replace("/", ".") except ValueError: if traverse_up: diff --git a/codeflash/context/code_context_extractor.py b/codeflash/context/code_context_extractor.py index 09c0c564a..4579e6b9b 100644 --- a/codeflash/context/code_context_extractor.py +++ b/codeflash/context/code_context_extractor.py @@ -334,7 +334,7 @@ def extract_code_markdown_context_from_files( helpers_of_fto.get(file_path, set()) | helpers_of_helpers.get(file_path, set()) ), ) - code_string_context = CodeString(code=code_context, file_path=file_path.relative_to(project_root_path)) + code_string_context = CodeString(code=code_context, file_path=file_path.resolve().relative_to(project_root_path.resolve())) code_context_markdown.code_strings.append(code_string_context) # Extract code from file paths containing helpers of helpers for file_path, helper_function_sources in helpers_of_helpers_no_overlap.items(): @@ -365,7 +365,7 @@ def extract_code_markdown_context_from_files( project_root=project_root_path, helper_functions=list(helpers_of_helpers_no_overlap.get(file_path, set())), ) - code_string_context = CodeString(code=code_context, file_path=file_path.relative_to(project_root_path)) + code_string_context = CodeString(code=code_context, file_path=file_path.resolve().relative_to(project_root_path.resolve())) code_context_markdown.code_strings.append(code_string_context) return code_context_markdown diff --git a/tests/test_code_context_extractor.py b/tests/test_code_context_extractor.py index 3a7de5d1c..6afe6931b 100644 --- a/tests/test_code_context_extractor.py +++ b/tests/test_code_context_extractor.py @@ -1827,7 +1827,7 @@ def get_system_details(): hashing_context = code_ctx.hashing_code_context # The expected contexts expected_read_write_context = f""" -```python:{main_file_path.relative_to(opt.args.project_root)} +```python:{main_file_path.resolve().relative_to(opt.args.project_root.resolve())} import utility_module class Calculator: @@ -2096,7 +2096,7 @@ def select_precision(precision, fallback_precision): else: return DEFAULT_PRECISION ``` -```python:{main_file_path.relative_to(opt.args.project_root)} +```python:{main_file_path.resolve().relative_to(opt.args.project_root.resolve())} import utility_module class Calculator: From 736faa1641420dd2c8d7cc6dbe542d29584cbfa4 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 15:09:49 -0700 Subject: [PATCH 08/34] add E2E gha & pytest-asyncio --- .github/workflows/e2e-async.yaml | 69 ++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 70 insertions(+) create mode 100644 .github/workflows/e2e-async.yaml diff --git a/.github/workflows/e2e-async.yaml b/.github/workflows/e2e-async.yaml new file mode 100644 index 000000000..e7d08091c --- /dev/null +++ b/.github/workflows/e2e-async.yaml @@ -0,0 +1,69 @@ +name: E2E - Async + +on: + pull_request: + paths: + - '**' # Trigger for all paths + + workflow_dispatch: + +jobs: + async-optimization: + # Dynamically determine if environment is needed only when workflow files change and contributor is external + environment: ${{ (github.event_name == 'workflow_dispatch' || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }} + + runs-on: ubuntu-latest + env: + CODEFLASH_AIS_SERVER: prod + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }} + COLUMNS: 110 + MAX_RETRIES: 3 + RETRY_DELAY: 5 + EXPECTED_IMPROVEMENT_PCT: 10 + CODEFLASH_END_TO_END: 1 + steps: + - name: 🛎️ Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate PR + run: | + # Check for any workflow changes + if git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | grep -q "^.github/workflows/"; then + echo "⚠️ Workflow changes detected." + + # Get the PR author + AUTHOR="${{ github.event.pull_request.user.login }}" + echo "PR Author: $AUTHOR" + + # Allowlist check + if [[ "$AUTHOR" == "misrasaurabh1" || "$AUTHOR" == "KRRT7" ]]; then + echo "✅ Authorized user ($AUTHOR). Proceeding." + elif [[ "${{ github.event.pull_request.state }}" == "open" ]]; then + echo "✅ PR triggered by 'pull_request_target' and is open. Assuming protection rules are in place. Proceeding." + else + echo "⛔ Unauthorized user ($AUTHOR) attempting to modify workflows. Exiting." + exit 1 + fi + else + echo "✅ No workflow file changes detected. Proceeding." + fi + + - name: Set up Python 3.11 for CLI + uses: astral-sh/setup-uv@v5 + with: + python-version: 3.11.6 + + - name: Install dependencies (CLI) + run: | + uv sync + + - name: Run Codeflash to optimize async code + id: optimize_async_code + run: | + uv run python tests/scripts/end_to_end_test_async.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 388568f90..20a0a1fe8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ dependencies = [ "pygls>=1.3.1", "codeflash-benchmark", "filelock", + "pytest-asyncio>=1.2.0", ] [project.urls] From 2d1696a2730966021976e840970f3e914c2fcaf3 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 15:21:57 -0700 Subject: [PATCH 09/34] restore from other branch --- codeflash/code_utils/static_analysis.py | 12 ++++++++--- codeflash/optimization/optimizer.py | 2 +- uv.lock | 27 ++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/codeflash/code_utils/static_analysis.py b/codeflash/code_utils/static_analysis.py index dbddb59f5..0151e29e7 100644 --- a/codeflash/code_utils/static_analysis.py +++ b/codeflash/code_utils/static_analysis.py @@ -128,13 +128,19 @@ def get_first_top_level_object_def_ast( def get_first_top_level_function_or_method_ast( function_name: str, parents: list[FunctionParent], node: ast.AST -) -> ast.FunctionDef | None: +) -> ast.FunctionDef | ast.AsyncFunctionDef | None: if not parents: - return get_first_top_level_object_def_ast(function_name, ast.FunctionDef, node) + result = get_first_top_level_object_def_ast(function_name, ast.FunctionDef, node) + if result is not None: + return result + return get_first_top_level_object_def_ast(function_name, ast.AsyncFunctionDef, node) if parents[0].type == "ClassDef" and ( class_node := get_first_top_level_object_def_ast(parents[0].name, ast.ClassDef, node) ): - return get_first_top_level_object_def_ast(function_name, ast.FunctionDef, class_node) + result = get_first_top_level_object_def_ast(function_name, ast.FunctionDef, class_node) + if result is not None: + return result + return get_first_top_level_object_def_ast(function_name, ast.AsyncFunctionDef, class_node) return None diff --git a/codeflash/optimization/optimizer.py b/codeflash/optimization/optimizer.py index ba713ea24..60a32c368 100644 --- a/codeflash/optimization/optimizer.py +++ b/codeflash/optimization/optimizer.py @@ -139,7 +139,7 @@ def get_optimizable_functions(self) -> tuple[dict[Path, list[FunctionToOptimize] def create_function_optimizer( self, function_to_optimize: FunctionToOptimize, - function_to_optimize_ast: ast.FunctionDef | None = None, + function_to_optimize_ast: ast.FunctionDef | ast.AsyncFunctionDef | None = None, function_to_tests: dict[str, set[FunctionCalledInTest]] | None = None, function_to_optimize_source_code: str | None = "", function_benchmark_timings: dict[str, dict[BenchmarkKey, float]] | None = None, diff --git a/uv.lock b/uv.lock index 0d7e8588b..8967d1b0e 100644 --- a/uv.lock +++ b/uv.lock @@ -60,6 +60,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, ] +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + [[package]] name = "blessed" version = "1.21.0" @@ -240,6 +249,7 @@ dependencies = [ { name = "pydantic" }, { name = "pygls" }, { name = "pytest" }, + { name = "pytest-asyncio" }, { name = "pytest-timeout" }, { name = "rich" }, { name = "sentry-sdk" }, @@ -300,6 +310,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=1.10.1" }, { name = "pygls", specifier = ">=1.3.1" }, { name = "pytest", specifier = ">=7.0.0,!=8.3.4" }, + { name = "pytest-asyncio", specifier = ">=1.2.0" }, { name = "pytest-timeout", specifier = ">=2.1.0" }, { name = "rich", specifier = ">=13.8.1" }, { name = "sentry-sdk", specifier = ">=1.40.6,<3.0.0" }, @@ -555,7 +566,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -1756,6 +1767,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + [[package]] name = "pytest-timeout" version = "2.4.0" From 2200b21d39ed02f26a1a9d2f435600fae36273c7 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 15:34:11 -0700 Subject: [PATCH 10/34] bring over changes from other branch --- codeflash/optimization/function_optimizer.py | 108 ++++++++++++++++--- codeflash/verification/pytest_plugin.py | 23 ++++ 2 files changed, 114 insertions(+), 17 deletions(-) diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index ccb7913eb..19ace3dfa 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -1472,6 +1472,19 @@ def establish_original_code_baseline( test_env = self.get_test_env(codeflash_loop_index=0, codeflash_test_iteration=0, codeflash_tracer_disable=1) + if self.function_to_optimize.is_async: + from codeflash.code_utils.instrument_existing_tests import ( + instrument_source_module_with_async_decorators, + ) + + success, instrumented_source = instrument_source_module_with_async_decorators( + self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.BEHAVIOR + ) + if success and instrumented_source: + with self.function_to_optimize.file_path.open("w", encoding="utf8") as f: + f.write(instrumented_source) + logger.debug(f"Applied async instrumentation to {self.function_to_optimize.file_path}") + # Instrument codeflash capture with progress_bar("Running tests to establish original code behavior..."): try: @@ -1511,15 +1524,38 @@ def establish_original_code_baseline( ) console.rule() with progress_bar("Running performance benchmarks..."): - benchmarking_results, _ = self.run_and_parse_tests( - testing_type=TestingMode.PERFORMANCE, - test_env=test_env, - test_files=self.test_files, - optimization_iteration=0, - testing_time=total_looping_time, - enable_coverage=False, - code_context=code_context, - ) + if self.function_to_optimize.is_async: + from codeflash.code_utils.instrument_existing_tests import ( + instrument_source_module_with_async_decorators, + ) + + success, instrumented_source = instrument_source_module_with_async_decorators( + self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.PERFORMANCE + ) + if success and instrumented_source: + with self.function_to_optimize.file_path.open("w", encoding="utf8") as f: + f.write(instrumented_source) + logger.debug( + f"Applied async performance instrumentation to {self.function_to_optimize.file_path}" + ) + + try: + benchmarking_results, _ = self.run_and_parse_tests( + testing_type=TestingMode.PERFORMANCE, + test_env=test_env, + test_files=self.test_files, + optimization_iteration=0, + testing_time=total_looping_time, + enable_coverage=False, + code_context=code_context, + ) + finally: + if self.function_to_optimize.is_async: + self.write_code_and_helpers( + self.function_to_optimize_source_code, + original_helper_code, + self.function_to_optimize.file_path, + ) else: benchmarking_results = TestResults() start_time: float = time.time() @@ -1614,6 +1650,21 @@ def run_optimized_candidate( candidate_helper_code = {} for module_abspath in original_helper_code: candidate_helper_code[module_abspath] = Path(module_abspath).read_text("utf-8") + if self.function_to_optimize.is_async: + from codeflash.code_utils.instrument_existing_tests import ( + instrument_source_module_with_async_decorators, + ) + + success, instrumented_source = instrument_source_module_with_async_decorators( + self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.BEHAVIOR + ) + if success and instrumented_source: + with self.function_to_optimize.file_path.open("w", encoding="utf8") as f: + f.write(instrumented_source) + logger.debug( + f"Applied async behavioral instrumentation to {self.function_to_optimize.file_path} for candidate {optimization_candidate_index}" + ) + try: instrument_codeflash_capture( self.function_to_optimize, file_path_to_helper_classes, self.test_cfg.tests_root @@ -1651,14 +1702,37 @@ def run_optimized_candidate( logger.info(f"loading|Running performance tests for candidate {optimization_candidate_index}...") if test_framework == "pytest": - candidate_benchmarking_results, _ = self.run_and_parse_tests( - testing_type=TestingMode.PERFORMANCE, - test_env=test_env, - test_files=self.test_files, - optimization_iteration=optimization_candidate_index, - testing_time=total_looping_time, - enable_coverage=False, - ) + # For async functions, instrument at definition site for performance benchmarking + if self.function_to_optimize.is_async: + from codeflash.code_utils.instrument_existing_tests import ( + instrument_source_module_with_async_decorators, + ) + + success, instrumented_source = instrument_source_module_with_async_decorators( + self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.PERFORMANCE + ) + if success and instrumented_source: + with self.function_to_optimize.file_path.open("w", encoding="utf8") as f: + f.write(instrumented_source) + logger.debug( + f"Applied async performance instrumentation to {self.function_to_optimize.file_path} for candidate {optimization_candidate_index}" + ) + + try: + candidate_benchmarking_results, _ = self.run_and_parse_tests( + testing_type=TestingMode.PERFORMANCE, + test_env=test_env, + test_files=self.test_files, + optimization_iteration=optimization_candidate_index, + testing_time=total_looping_time, + enable_coverage=False, + ) + finally: + # Restore original source if we instrumented it + if self.function_to_optimize.is_async: + self.write_code_and_helpers( + candidate_fto_code, candidate_helper_code, self.function_to_optimize.file_path + ) loop_count = ( max(all_loop_indices) if ( diff --git a/codeflash/verification/pytest_plugin.py b/codeflash/verification/pytest_plugin.py index 85cd4d13c..1f12aecbc 100644 --- a/codeflash/verification/pytest_plugin.py +++ b/codeflash/verification/pytest_plugin.py @@ -450,3 +450,26 @@ def make_progress_id(i: int, n: int = count) -> str: metafunc.parametrize( "__pytest_loop_step_number", range(count), indirect=True, ids=make_progress_id, scope=scope ) + + @pytest.hookimpl(tryfirst=True) + def pytest_runtest_setup(self, item: pytest.Item) -> None: + """Set test context environment variables before each test.""" + test_module_name = item.module.__name__ if item.module else "unknown_module" + + test_class_name = None + if item.cls: + test_class_name = item.cls.__name__ + + test_function_name = item.name + if "[" in test_function_name: + test_function_name = test_function_name.split("[", 1)[0] + + os.environ["CODEFLASH_TEST_MODULE"] = test_module_name + os.environ["CODEFLASH_TEST_CLASS"] = test_class_name or "" + os.environ["CODEFLASH_TEST_FUNCTION"] = test_function_name + + @pytest.hookimpl(trylast=True) + def pytest_runtest_teardown(self, item: pytest.Item) -> None: # noqa: ARG002 + """Clean up test context environment variables after each test.""" + for var in ["CODEFLASH_TEST_MODULE", "CODEFLASH_TEST_CLASS", "CODEFLASH_TEST_FUNCTION"]: + os.environ.pop(var, None) From 5f7e11d55d9458009b3f5db6f18573c097f71ce8 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 15:40:42 -0700 Subject: [PATCH 11/34] Add missing async throughput parameters to AI service explanation --- codeflash/api/aiservice.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codeflash/api/aiservice.py b/codeflash/api/aiservice.py index de3e2928a..a731bf5a6 100644 --- a/codeflash/api/aiservice.py +++ b/codeflash/api/aiservice.py @@ -302,6 +302,9 @@ def get_new_explanation( # noqa: D417 annotated_tests: str, optimization_id: str, original_explanation: str, + original_throughput: str | None = None, + optimized_throughput: str | None = None, + throughput_improvement: str | None = None, ) -> str: """Optimize the given python code for performance by making a request to the Django endpoint. @@ -318,6 +321,9 @@ def get_new_explanation( # noqa: D417 - annotated_tests: str - test functions annotated with runtime - optimization_id: str - unique id of opt candidate - original_explanation: str - original_explanation generated for the opt candidate + - original_throughput: str | None - throughput for the baseline code (operations per second) + - optimized_throughput: str | None - throughput for the optimized code (operations per second) + - throughput_improvement: str | None - throughput improvement percentage Returns ------- @@ -337,6 +343,9 @@ def get_new_explanation( # noqa: D417 "optimization_id": optimization_id, "original_explanation": original_explanation, "dependency_code": dependency_code, + "original_throughput": original_throughput, + "optimized_throughput": optimized_throughput, + "throughput_improvement": throughput_improvement, } logger.info("loading|Generating explanation") console.rule() From 11ede6e899a4f1cd03028d1243ef58d846d969c0 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 15:45:27 -0700 Subject: [PATCH 12/34] Update code_utils.py --- codeflash/code_utils/code_utils.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/codeflash/code_utils/code_utils.py b/codeflash/code_utils/code_utils.py index 4dc02eb82..002113539 100644 --- a/codeflash/code_utils/code_utils.py +++ b/codeflash/code_utils/code_utils.py @@ -268,14 +268,6 @@ def validate_python_code(code: str) -> str: return code -def has_any_async_functions(code: str) -> bool: - try: - module = ast.parse(code) - except SyntaxError: - return False - return any(isinstance(node, ast.AsyncFunctionDef) for node in ast.walk(module)) - - def cleanup_paths(paths: list[Path]) -> None: for path in paths: if path and path.exists(): From a65f02629ea53b9de06cba7a791ffd24da9a5a13 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:03:27 -0700 Subject: [PATCH 13/34] restore fields --- codeflash/models/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codeflash/models/models.py b/codeflash/models/models.py index c1a563672..73705be94 100644 --- a/codeflash/models/models.py +++ b/codeflash/models/models.py @@ -103,6 +103,7 @@ class BestOptimization(BaseModel): winning_benchmarking_test_results: TestResults winning_replay_benchmarking_test_results: Optional[TestResults] = None line_profiler_test_results: dict + async_throughput: Optional[int] = None @dataclass(frozen=True) @@ -277,6 +278,7 @@ class OptimizedCandidateResult(BaseModel): replay_benchmarking_test_results: Optional[dict[BenchmarkKey, TestResults]] = None optimization_candidate_index: int total_candidate_timing: int + async_throughput: Optional[int] = None class GeneratedTests(BaseModel): @@ -383,6 +385,7 @@ class OriginalCodeBaseline(BaseModel): line_profile_results: dict runtime: int coverage_results: Optional[CoverageData] + async_throughput: Optional[int] = None class CoverageStatus(Enum): @@ -545,6 +548,7 @@ class TestResults(BaseModel): # noqa: PLW1641 # also we don't support deletion of test results elements - caution is advised test_results: list[FunctionTestInvocation] = [] test_result_idx: dict[str, int] = {} + perf_stdout: Optional[str] = None def add(self, function_test_invocation: FunctionTestInvocation) -> None: unique_id = function_test_invocation.unique_invocation_loop_id From 1fe7bd12cb181682f45a0e3bde7a3c2905a96ecf Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:05:42 -0700 Subject: [PATCH 14/34] update type --- codeflash/optimization/function_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index 19ace3dfa..b1df6fe2d 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -198,7 +198,7 @@ def __init__( test_cfg: TestConfig, function_to_optimize_source_code: str = "", function_to_tests: dict[str, set[FunctionCalledInTest]] | None = None, - function_to_optimize_ast: ast.FunctionDef | None = None, + function_to_optimize_ast: ast.FunctionDef | ast.AsyncFunctionDef | None = None, aiservice_client: AiServiceClient | None = None, function_benchmark_timings: dict[BenchmarkKey, int] | None = None, total_benchmark_timings: dict[BenchmarkKey, int] | None = None, From 6bd39f7791d3b0e68e175fe79b57695698d0b199 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:14:33 -0700 Subject: [PATCH 15/34] fix cov method --- codeflash/verification/coverage_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codeflash/verification/coverage_utils.py b/codeflash/verification/coverage_utils.py index c9044a44d..63ba2c9d6 100644 --- a/codeflash/verification/coverage_utils.py +++ b/codeflash/verification/coverage_utils.py @@ -38,10 +38,10 @@ def load_from_sqlite_database( cov = Coverage(data_file=database_path, config_file=config_path, data_suffix=True, auto_data=True, branch=True) - if not database_path.stat().st_size or not database_path.exists(): + if not database_path.exists() or not database_path.stat().st_size: logger.debug(f"Coverage database {database_path} is empty or does not exist") sentry_sdk.capture_message(f"Coverage database {database_path} is empty or does not exist") - return CoverageUtils.create_empty(source_code_path, function_name, code_context) + return CoverageData.create_empty(source_code_path, function_name, code_context) cov.load() reporter = JsonReporter(cov) @@ -51,7 +51,7 @@ def load_from_sqlite_database( reporter.report(morfs=[source_code_path.as_posix()], outfile=f) except NoDataError: sentry_sdk.capture_message(f"No coverage data found for {function_name} in {source_code_path}") - return CoverageUtils.create_empty(source_code_path, function_name, code_context) + return CoverageData.create_empty(source_code_path, function_name, code_context) with temp_json_file.open() as f: original_coverage_data = json.load(f) From c6c9d9559fde57a85ea376c34c0bb900e0c09292 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:25:28 -0700 Subject: [PATCH 16/34] few missing things --- codeflash/verification/parse_test_output.py | 24 ++ tests/test_add_runtime_comments.py | 208 ++++++++++++- tests/test_code_context_extractor.py | 160 +++++++++- tests/test_code_replacement.py | 312 +++++++++++--------- tests/test_code_utils.py | 134 +++++++-- 5 files changed, 669 insertions(+), 169 deletions(-) diff --git a/codeflash/verification/parse_test_output.py b/codeflash/verification/parse_test_output.py index 4af1eec50..3b19d94c8 100644 --- a/codeflash/verification/parse_test_output.py +++ b/codeflash/verification/parse_test_output.py @@ -40,6 +40,30 @@ def parse_func(file_path: Path) -> XMLParser: matches_re_end = re.compile(r"!######(.*?):(.*?)([^\.:]*?):(.*?):(.*?):(.*?)######!") +start_pattern = re.compile(r"!\$######([^:]*):([^:]*):([^:]*):([^:]*):([^:]+)######\$!") +end_pattern = re.compile(r"!######([^:]*):([^:]*):([^:]*):([^:]*):([^:]+):([^:]+)######!") + + +def calculate_function_throughput_from_test_results(test_results: TestResults, function_name: str) -> int: + """Calculate function throughput from TestResults by extracting performance stdout. + + A completed execution is defined as having both a start tag and matching end tag from performance wrappers. + Start: !$######test_module:test_function:function_name:loop_index:iteration_id######$! + End: !######test_module:test_function:function_name:loop_index:iteration_id:duration######! + """ + start_matches = start_pattern.findall(test_results.perf_stdout or "") + end_matches = end_pattern.findall(test_results.perf_stdout or "") + + end_matches_truncated = [end_match[:5] for end_match in end_matches] + end_matches_set = set(end_matches_truncated) + + function_throughput = 0 + for start_match in start_matches: + if start_match in end_matches_set and len(start_match) > 2 and start_match[2] == function_name: + function_throughput += 1 + return function_throughput + + def parse_test_return_values_bin(file_location: Path, test_files: TestFiles, test_config: TestConfig) -> TestResults: test_results = TestResults() if not file_location.exists(): diff --git a/tests/test_add_runtime_comments.py b/tests/test_add_runtime_comments.py index da6e49373..d9c36219a 100644 --- a/tests/test_add_runtime_comments.py +++ b/tests/test_add_runtime_comments.py @@ -1902,4 +1902,210 @@ def test_bubble_sort(input, expected_output): # Check that comments were added modified_source = result.generated_tests[0].generated_original_test_source - assert modified_source == expected \ No newline at end of file + assert modified_source == expected + + def test_async_basic_runtime_comment_addition(self, test_config): + """Test basic functionality of adding runtime comments to async test functions.""" + os.chdir(test_config.project_root_path) + test_source = """async def test_async_bubble_sort(): + codeflash_output = await async_bubble_sort([3, 1, 2]) + assert codeflash_output == [1, 2, 3] +""" + + generated_test = GeneratedTests( + generated_original_test_source=test_source, + instrumented_behavior_test_source="", + instrumented_perf_test_source="", + behavior_file_path=test_config.tests_root / "test_module__unit_test_0.py", + perf_file_path=test_config.tests_root / "test_perf.py", + ) + generated_tests = GeneratedTestsList(generated_tests=[generated_test]) + + original_test_results = TestResults() + optimized_test_results = TestResults() + + original_invocation = self.create_test_invocation("test_async_bubble_sort", 500_000, iteration_id='0') # 500μs + optimized_invocation = self.create_test_invocation("test_async_bubble_sort", 300_000, iteration_id='0') # 300μs + + original_test_results.add(original_invocation) + optimized_test_results.add(optimized_invocation) + original_runtimes = original_test_results.usable_runtime_data_by_test_case() + optimized_runtimes = optimized_test_results.usable_runtime_data_by_test_case() + result = add_runtime_comments_to_generated_tests(generated_tests, original_runtimes, optimized_runtimes) + + modified_source = result.generated_tests[0].generated_original_test_source + assert "# 500μs -> 300μs" in modified_source + assert "codeflash_output = await async_bubble_sort([3, 1, 2]) # 500μs -> 300μs" in modified_source + + def test_async_multiple_test_functions(self, test_config): + os.chdir(test_config.project_root_path) + test_source = """async def test_async_bubble_sort(): + codeflash_output = await async_quick_sort([3, 1, 2]) + assert codeflash_output == [1, 2, 3] + +async def test_async_quick_sort(): + codeflash_output = await async_quick_sort([5, 2, 8]) + assert codeflash_output == [2, 5, 8] + +def helper_function(): + return "not a test" +""" + generated_test = GeneratedTests( + generated_original_test_source=test_source, + instrumented_behavior_test_source="", + instrumented_perf_test_source="", + behavior_file_path=test_config.tests_root / "test_module__unit_test_0.py", + perf_file_path=test_config.tests_root / "test_perf.py" + ) + + generated_tests = GeneratedTestsList(generated_tests=[generated_test]) + + original_test_results = TestResults() + optimized_test_results = TestResults() + + original_test_results.add(self.create_test_invocation("test_async_bubble_sort", 500_000, iteration_id='0')) + original_test_results.add(self.create_test_invocation("test_async_quick_sort", 800_000, iteration_id='0')) + + optimized_test_results.add(self.create_test_invocation("test_async_bubble_sort", 300_000, iteration_id='0')) + optimized_test_results.add(self.create_test_invocation("test_async_quick_sort", 600_000, iteration_id='0')) + + original_runtimes = original_test_results.usable_runtime_data_by_test_case() + optimized_runtimes = optimized_test_results.usable_runtime_data_by_test_case() + + result = add_runtime_comments_to_generated_tests(generated_tests, original_runtimes, optimized_runtimes) + + modified_source = result.generated_tests[0].generated_original_test_source + + assert "# 500μs -> 300μs" in modified_source + assert "# 800μs -> 600μs" in modified_source + assert ( + "helper_function():" in modified_source + and "# " not in modified_source.split("helper_function():")[1].split("\n")[0] + ) + + def test_async_class_method(self, test_config): + os.chdir(test_config.project_root_path) + test_source = '''class TestAsyncClass: + async def test_async_function(self): + codeflash_output = await some_async_function() + assert codeflash_output == expected +''' + generated_test = GeneratedTests( + generated_original_test_source=test_source, + instrumented_behavior_test_source="", + instrumented_perf_test_source="", + behavior_file_path=test_config.tests_root / "test_module__unit_test_0.py", + perf_file_path=test_config.tests_root / "test_perf.py" + ) + + generated_tests = GeneratedTestsList(generated_tests=[generated_test]) + + invocation_id = InvocationId( + test_module_path="tests.test_module__unit_test_0", + test_class_name="TestAsyncClass", + test_function_name="test_async_function", + function_getting_tested="some_async_function", + iteration_id="0", + ) + + original_runtimes = {invocation_id: [2000000000]} # 2s in nanoseconds + optimized_runtimes = {invocation_id: [1000000000]} # 1s in nanoseconds + + result = add_runtime_comments_to_generated_tests(generated_tests, original_runtimes, optimized_runtimes) + + expected_source = '''class TestAsyncClass: + async def test_async_function(self): + codeflash_output = await some_async_function() # 2.00s -> 1.00s (100% faster) + assert codeflash_output == expected +''' + + assert len(result.generated_tests) == 1 + assert result.generated_tests[0].generated_original_test_source == expected_source + + def test_async_mixed_sync_and_async_functions(self, test_config): + os.chdir(test_config.project_root_path) + test_source = """def test_sync_function(): + codeflash_output = sync_function([1, 2, 3]) + assert codeflash_output == [1, 2, 3] + +async def test_async_function(): + codeflash_output = await async_function([4, 5, 6]) + assert codeflash_output == [4, 5, 6] + +def test_another_sync(): + result = another_sync_func() + assert result is True +""" + generated_test = GeneratedTests( + generated_original_test_source=test_source, + instrumented_behavior_test_source="", + instrumented_perf_test_source="", + behavior_file_path=test_config.tests_root / "test_module__unit_test_0.py", + perf_file_path=test_config.tests_root / "test_perf.py" + ) + + generated_tests = GeneratedTestsList(generated_tests=[generated_test]) + + original_test_results = TestResults() + optimized_test_results = TestResults() + + # Add test invocations for all test functions + original_test_results.add(self.create_test_invocation("test_sync_function", 400_000, iteration_id='0')) + original_test_results.add(self.create_test_invocation("test_async_function", 600_000, iteration_id='0')) + original_test_results.add(self.create_test_invocation("test_another_sync", 200_000, iteration_id='0')) + + optimized_test_results.add(self.create_test_invocation("test_sync_function", 200_000, iteration_id='0')) + optimized_test_results.add(self.create_test_invocation("test_async_function", 300_000, iteration_id='0')) + optimized_test_results.add(self.create_test_invocation("test_another_sync", 100_000, iteration_id='0')) + + original_runtimes = original_test_results.usable_runtime_data_by_test_case() + optimized_runtimes = optimized_test_results.usable_runtime_data_by_test_case() + + result = add_runtime_comments_to_generated_tests(generated_tests, original_runtimes, optimized_runtimes) + + modified_source = result.generated_tests[0].generated_original_test_source + + assert "# 400μs -> 200μs" in modified_source + assert "# 600μs -> 300μs" in modified_source + assert "# 200μs -> 100μs" in modified_source + + assert "async def test_async_function():" in modified_source + assert "await async_function([4, 5, 6])" in modified_source + + def test_async_complex_await_patterns(self, test_config): + os.chdir(test_config.project_root_path) + test_source = """async def test_complex_async(): + # Multiple await calls + result1 = await async_func1() + codeflash_output = await async_func2(result1) + result3 = await async_func3(codeflash_output) + assert result3 == expected + + # Await in context manager + async with async_context() as ctx: + final_result = await ctx.process() + assert final_result is not None +""" + generated_test = GeneratedTests( + generated_original_test_source=test_source, + instrumented_behavior_test_source="", + instrumented_perf_test_source="", + behavior_file_path=test_config.tests_root / "test_module__unit_test_0.py", + perf_file_path=test_config.tests_root / "test_perf.py" + ) + + generated_tests = GeneratedTestsList(generated_tests=[generated_test]) + + original_test_results = TestResults() + optimized_test_results = TestResults() + + original_test_results.add(self.create_test_invocation("test_complex_async", 750_000, iteration_id='1')) # 750μs + optimized_test_results.add(self.create_test_invocation("test_complex_async", 450_000, iteration_id='1')) # 450μs + + original_runtimes = original_test_results.usable_runtime_data_by_test_case() + optimized_runtimes = optimized_test_results.usable_runtime_data_by_test_case() + + result = add_runtime_comments_to_generated_tests(generated_tests, original_runtimes, optimized_runtimes) + + modified_source = result.generated_tests[0].generated_original_test_source + assert "# 750μs -> 450μs" in modified_source \ No newline at end of file diff --git a/tests/test_code_context_extractor.py b/tests/test_code_context_extractor.py index 6afe6931b..b6b2aa219 100644 --- a/tests/test_code_context_extractor.py +++ b/tests/test_code_context_extractor.py @@ -12,7 +12,7 @@ from codeflash.models.models import FunctionParent from codeflash.optimization.optimizer import Optimizer from codeflash.code_utils.code_replacer import replace_functions_and_add_imports -from codeflash.code_utils.code_extractor import add_global_assignments +from codeflash.code_utils.code_extractor import add_global_assignments, GlobalAssignmentCollector class HelperClass: @@ -1800,9 +1800,10 @@ def get_system_details(): # Set up the optimizer file_path = main_file_path.resolve() + project_root = package_dir.resolve() opt = Optimizer( Namespace( - project_root=package_dir.resolve(), + project_root=project_root, disable_telemetry=True, tests_root="tests", test_framework="pytest", @@ -1826,8 +1827,10 @@ def get_system_details(): read_write_context, read_only_context = code_ctx.read_writable_code, code_ctx.read_only_context_code hashing_context = code_ctx.hashing_code_context # The expected contexts + # Resolve both paths to handle symlink issues on macOS + relative_path = file_path.relative_to(project_root) expected_read_write_context = f""" -```python:{main_file_path.resolve().relative_to(opt.args.project_root.resolve())} +```python:{relative_path} import utility_module class Calculator: @@ -2045,9 +2048,10 @@ def get_system_details(): # Set up the optimizer file_path = main_file_path.resolve() + project_root = package_dir.resolve() opt = Optimizer( Namespace( - project_root=package_dir.resolve(), + project_root=project_root, disable_telemetry=True, tests_root="tests", test_framework="pytest", @@ -2070,6 +2074,7 @@ def get_system_details(): code_ctx = get_code_optimization_context(function_to_optimize, opt.args.project_root) read_write_context, read_only_context = code_ctx.read_writable_code, code_ctx.read_only_context_code # The expected contexts + relative_path = file_path.relative_to(project_root) expected_read_write_context = f""" ```python:utility_module.py # Function that will be used in the main code @@ -2096,7 +2101,7 @@ def select_precision(precision, fallback_precision): else: return DEFAULT_PRECISION ``` -```python:{main_file_path.resolve().relative_to(opt.args.project_root.resolve())} +```python:{relative_path} import utility_module class Calculator: @@ -2477,3 +2482,148 @@ def test_circular_deps(): assert "import ApiClient" not in new_code, "Error: Circular dependency found" assert "import urllib.parse" in new_code, "Make sure imports for optimization global assignments exist" +def test_global_assignment_collector_with_async_function(): + """Test GlobalAssignmentCollector correctly identifies global assignments outside async functions.""" + import libcst as cst + + source_code = """ +# Global assignment +GLOBAL_VAR = "global_value" +OTHER_GLOBAL = 42 + +async def async_function(): + # This should not be collected (inside async function) + local_var = "local_value" + INNER_ASSIGNMENT = "should_not_be_global" + return local_var + +# Another global assignment +ANOTHER_GLOBAL = "another_global" +""" + + tree = cst.parse_module(source_code) + collector = GlobalAssignmentCollector() + tree.visit(collector) + + # Should collect global assignments but not the ones inside async function + assert len(collector.assignments) == 3 + assert "GLOBAL_VAR" in collector.assignments + assert "OTHER_GLOBAL" in collector.assignments + assert "ANOTHER_GLOBAL" in collector.assignments + + # Should not collect assignments from inside async function + assert "local_var" not in collector.assignments + assert "INNER_ASSIGNMENT" not in collector.assignments + + # Verify assignment order + expected_order = ["GLOBAL_VAR", "OTHER_GLOBAL", "ANOTHER_GLOBAL"] + assert collector.assignment_order == expected_order + + +def test_global_assignment_collector_nested_async_functions(): + """Test GlobalAssignmentCollector handles nested async functions correctly.""" + import libcst as cst + + source_code = """ +# Global assignment +CONFIG = {"key": "value"} + +def sync_function(): + # Inside sync function - should not be collected + sync_local = "sync" + + async def nested_async(): + # Inside nested async function - should not be collected + nested_var = "nested" + return nested_var + + return sync_local + +async def async_function(): + # Inside async function - should not be collected + async_local = "async" + + def nested_sync(): + # Inside nested function - should not be collected + deeply_nested = "deep" + return deeply_nested + + return async_local + +# Another global assignment +FINAL_GLOBAL = "final" +""" + + tree = cst.parse_module(source_code) + collector = GlobalAssignmentCollector() + tree.visit(collector) + + # Should only collect global-level assignments + assert len(collector.assignments) == 2 + assert "CONFIG" in collector.assignments + assert "FINAL_GLOBAL" in collector.assignments + + # Should not collect any assignments from inside functions + assert "sync_local" not in collector.assignments + assert "nested_var" not in collector.assignments + assert "async_local" not in collector.assignments + assert "deeply_nested" not in collector.assignments + + +def test_global_assignment_collector_mixed_async_sync_with_classes(): + """Test GlobalAssignmentCollector with async functions, sync functions, and classes.""" + import libcst as cst + + source_code = """ +# Global assignments +GLOBAL_CONSTANT = "constant" + +class TestClass: + # Class-level assignment - should not be collected + class_var = "class_value" + + def sync_method(self): + # Method assignment - should not be collected + method_var = "method" + return method_var + + async def async_method(self): + # Async method assignment - should not be collected + async_method_var = "async_method" + return async_method_var + +def sync_function(): + # Function assignment - should not be collected + func_var = "function" + return func_var + +async def async_function(): + # Async function assignment - should not be collected + async_func_var = "async_function" + return async_func_var + +# More global assignments +ANOTHER_CONSTANT = 100 +FINAL_ASSIGNMENT = {"data": "value"} +""" + + tree = cst.parse_module(source_code) + collector = GlobalAssignmentCollector() + tree.visit(collector) + + # Should only collect global-level assignments + assert len(collector.assignments) == 3 + assert "GLOBAL_CONSTANT" in collector.assignments + assert "ANOTHER_CONSTANT" in collector.assignments + assert "FINAL_ASSIGNMENT" in collector.assignments + + # Should not collect assignments from inside any scoped blocks + assert "class_var" not in collector.assignments + assert "method_var" not in collector.assignments + assert "async_method_var" not in collector.assignments + assert "func_var" not in collector.assignments + assert "async_func_var" not in collector.assignments + + # Verify correct order + expected_order = ["GLOBAL_CONSTANT", "ANOTHER_CONSTANT", "FINAL_ASSIGNMENT"] + assert collector.assignment_order == expected_order diff --git a/tests/test_code_replacement.py b/tests/test_code_replacement.py index f7bfaace3..37c64c4da 100644 --- a/tests/test_code_replacement.py +++ b/tests/test_code_replacement.py @@ -12,6 +12,7 @@ is_zero_diff, replace_functions_and_add_imports, replace_functions_in_file, + OptimFunctionCollector, ) from codeflash.discovery.functions_to_optimize import FunctionToOptimize from codeflash.models.models import CodeOptimizationContext, CodeStringsMarkdown, FunctionParent @@ -1707,6 +1708,7 @@ def new_function2(value): """ expected_code = """import numpy as np +print("Hello world") a=2 print("Hello world") def some_fn(): @@ -1782,6 +1784,7 @@ def new_function2(value): """ expected_code = """import numpy as np +print("Hello world") print("Hello world") def some_fn(): a=np.zeros(10) @@ -1860,6 +1863,7 @@ def new_function2(value): """ expected_code = """import numpy as np +print("Hello world") a=3 print("Hello world") def some_fn(): @@ -1937,6 +1941,7 @@ def new_function2(value): """ expected_code = """import numpy as np +print("Hello world") a=2 print("Hello world") def some_fn(): @@ -2015,6 +2020,7 @@ def new_function2(value): """ expected_code = """import numpy as np +print("Hello world") a=3 print("Hello world") def some_fn(): @@ -2101,6 +2107,7 @@ def new_function2(value): a = 6 +print("Hello world") if 2<3: a=4 else: @@ -3448,156 +3455,173 @@ def hydrate_input_text_actions_with_field_names( assert new_code == expected -def test_duplicate_global_assignments_when_reverting_helpers(): - root_dir = Path(__file__).parent.parent.resolve() - main_file = Path(root_dir / "code_to_optimize/temp_main.py").resolve() - original_code = '''"""Chunking objects not specific to a particular chunking strategy.""" -from __future__ import annotations -import collections -import copy -from typing import Any, Callable, DefaultDict, Iterable, Iterator, cast -import regex -from typing_extensions import Self, TypeAlias -from unstructured.utils import lazyproperty -from unstructured.documents.elements import Element -# ================================================================================================ -# MODEL -# ================================================================================================ -CHUNK_MAX_CHARS_DEFAULT: int = 500 -# ================================================================================================ -# PRE-CHUNKER -# ================================================================================================ -class PreChunker: - """Gathers sequential elements into pre-chunks as length constraints allow. - The pre-chunker's responsibilities are: - - **Segregate semantic units.** Identify semantic unit boundaries and segregate elements on - either side of those boundaries into different sections. In this case, the primary indicator - of a semantic boundary is a `Title` element. A page-break (change in page-number) is also a - semantic boundary when `multipage_sections` is `False`. - - **Minimize chunk count for each semantic unit.** Group the elements within a semantic unit - into sections as big as possible without exceeding the chunk window size. - - **Minimize chunks that must be split mid-text.** Precompute the text length of each section - and only produce a section that exceeds the chunk window size when there is a single element - with text longer than that window. - A Table element is placed into a section by itself. CheckBox elements are dropped. - The "by-title" strategy specifies breaking on section boundaries; a `Title` element indicates - a new "section", hence the "by-title" designation. - """ - def __init__(self, elements: Iterable[Element], opts: ChunkingOptions): - self._elements = elements - self._opts = opts - @lazyproperty - def _boundary_predicates(self) -> tuple[BoundaryPredicate, ...]: - """The semantic-boundary detectors to be applied to break pre-chunks.""" - return self._opts.boundary_predicates - def _is_in_new_semantic_unit(self, element: Element) -> bool: - """True when `element` begins a new semantic unit such as a section or page.""" - # -- all detectors need to be called to update state and avoid double counting - # -- boundaries that happen to coincide, like Table and new section on same element. - # -- Using `any()` would short-circuit on first True. - semantic_boundaries = [pred(element) for pred in self._boundary_predicates] - return any(semantic_boundaries) -''' - main_file.write_text(original_code, encoding="utf-8") - optim_code = f'''```python:{main_file.relative_to(root_dir)} -# ================================================================================================ -# PRE-CHUNKER -# ================================================================================================ -from __future__ import annotations -from typing import Iterable -from unstructured.documents.elements import Element -from unstructured.utils import lazyproperty -class PreChunker: - def __init__(self, elements: Iterable[Element], opts: ChunkingOptions): - self._elements = elements - self._opts = opts - @lazyproperty - def _boundary_predicates(self) -> tuple[BoundaryPredicate, ...]: - """The semantic-boundary detectors to be applied to break pre-chunks.""" - return self._opts.boundary_predicates - def _is_in_new_semantic_unit(self, element: Element) -> bool: - """True when `element` begins a new semantic unit such as a section or page.""" - # Use generator expression for lower memory usage and avoid building intermediate list - for pred in self._boundary_predicates: - if pred(element): - return True - return False -``` -''' +# OptimFunctionCollector async function tests +def test_optim_function_collector_with_async_functions(): + """Test OptimFunctionCollector correctly collects async functions.""" + import libcst as cst + + source_code = """ +def sync_function(): + return "sync" - func = FunctionToOptimize(function_name="_is_in_new_semantic_unit", parents=[FunctionParent("PreChunker", "ClassDef")], file_path=main_file) - test_config = TestConfig( - tests_root=root_dir / "tests/pytest", - tests_project_rootdir=root_dir, - project_root_path=root_dir, - test_framework="pytest", - pytest_cmd="pytest", +async def async_function(): + return "async" + +class TestClass: + def sync_method(self): + return "sync_method" + + async def async_method(self): + return "async_method" +""" + + tree = cst.parse_module(source_code) + collector = OptimFunctionCollector( + function_names={(None, "sync_function"), (None, "async_function"), ("TestClass", "sync_method"), ("TestClass", "async_method")}, + preexisting_objects=None ) - func_optimizer = FunctionOptimizer(function_to_optimize=func, test_cfg=test_config) - code_context: CodeOptimizationContext = func_optimizer.get_code_optimization_context().unwrap() + tree.visit(collector) + + # Should collect both sync and async functions + assert len(collector.modified_functions) == 4 + assert (None, "sync_function") in collector.modified_functions + assert (None, "async_function") in collector.modified_functions + assert ("TestClass", "sync_method") in collector.modified_functions + assert ("TestClass", "async_method") in collector.modified_functions - original_helper_code: dict[Path, str] = {} - helper_function_paths = {hf.file_path for hf in code_context.helper_functions} - for helper_function_path in helper_function_paths: - with helper_function_path.open(encoding="utf8") as f: - helper_code = f.read() - original_helper_code[helper_function_path] = helper_code - func_optimizer.args = Args() - func_optimizer.replace_function_and_helpers_with_optimized_code( - code_context=code_context, optimized_code=CodeStringsMarkdown.parse_markdown_code(optim_code), original_helper_code=original_helper_code +def test_optim_function_collector_new_async_functions(): + """Test OptimFunctionCollector identifies new async functions not in preexisting objects.""" + import libcst as cst + + source_code = """ +def existing_function(): + return "existing" + +async def new_async_function(): + return "new_async" + +def new_sync_function(): + return "new_sync" + +class ExistingClass: + async def new_class_async_method(self): + return "new_class_async" +""" + + # Only existing_function is in preexisting objects + preexisting_objects = {("existing_function", ())} + + tree = cst.parse_module(source_code) + collector = OptimFunctionCollector( + function_names=set(), # Not looking for specific functions + preexisting_objects=preexisting_objects ) + tree.visit(collector) + + # Should identify new functions (both sync and async) + assert len(collector.new_functions) == 2 + function_names = [func.name.value for func in collector.new_functions] + assert "new_async_function" in function_names + assert "new_sync_function" in function_names + + # Should identify new class methods + assert "ExistingClass" in collector.new_class_functions + assert len(collector.new_class_functions["ExistingClass"]) == 1 + assert collector.new_class_functions["ExistingClass"][0].name.value == "new_class_async_method" - new_code = main_file.read_text(encoding="utf-8") - main_file.unlink(missing_ok=True) +def test_optim_function_collector_mixed_scenarios(): + """Test OptimFunctionCollector with complex mix of sync/async functions and classes.""" + import libcst as cst + + source_code = """ +# Global functions +def global_sync(): + pass - expected = '''"""Chunking objects not specific to a particular chunking strategy.""" -from __future__ import annotations -import collections -import copy -from typing import Any, Callable, DefaultDict, Iterable, Iterator, cast -import regex -from typing_extensions import Self, TypeAlias -from unstructured.utils import lazyproperty -from unstructured.documents.elements import Element -# ================================================================================================ -# MODEL -# ================================================================================================ -CHUNK_MAX_CHARS_DEFAULT: int = 500 -# ================================================================================================ -# PRE-CHUNKER -# ================================================================================================ -class PreChunker: - """Gathers sequential elements into pre-chunks as length constraints allow. - The pre-chunker's responsibilities are: - - **Segregate semantic units.** Identify semantic unit boundaries and segregate elements on - either side of those boundaries into different sections. In this case, the primary indicator - of a semantic boundary is a `Title` element. A page-break (change in page-number) is also a - semantic boundary when `multipage_sections` is `False`. - - **Minimize chunk count for each semantic unit.** Group the elements within a semantic unit - into sections as big as possible without exceeding the chunk window size. - - **Minimize chunks that must be split mid-text.** Precompute the text length of each section - and only produce a section that exceeds the chunk window size when there is a single element - with text longer than that window. - A Table element is placed into a section by itself. CheckBox elements are dropped. - The "by-title" strategy specifies breaking on section boundaries; a `Title` element indicates - a new "section", hence the "by-title" designation. - """ - def __init__(self, elements: Iterable[Element], opts: ChunkingOptions): - self._elements = elements - self._opts = opts - @lazyproperty - def _boundary_predicates(self) -> tuple[BoundaryPredicate, ...]: - """The semantic-boundary detectors to be applied to break pre-chunks.""" - return self._opts.boundary_predicates - def _is_in_new_semantic_unit(self, element: Element) -> bool: - """True when `element` begins a new semantic unit such as a section or page.""" - # Use generator expression for lower memory usage and avoid building intermediate list - for pred in self._boundary_predicates: - if pred(element): - return True - return False -''' - assert new_code == expected \ No newline at end of file +async def global_async(): + pass + +class ParentClass: + def __init__(self): + pass + + def sync_method(self): + pass + + async def async_method(self): + pass + +class ChildClass: + async def child_async_method(self): + pass + + def child_sync_method(self): + pass +""" + + # Looking for specific functions + function_names = { + (None, "global_sync"), + (None, "global_async"), + ("ParentClass", "sync_method"), + ("ParentClass", "async_method"), + ("ChildClass", "child_async_method") + } + + tree = cst.parse_module(source_code) + collector = OptimFunctionCollector( + function_names=function_names, + preexisting_objects=None + ) + tree.visit(collector) + + # Should collect all specified functions (mix of sync and async) + assert len(collector.modified_functions) == 5 + assert (None, "global_sync") in collector.modified_functions + assert (None, "global_async") in collector.modified_functions + assert ("ParentClass", "sync_method") in collector.modified_functions + assert ("ParentClass", "async_method") in collector.modified_functions + assert ("ChildClass", "child_async_method") in collector.modified_functions + + # Should collect __init__ method + assert "ParentClass" in collector.modified_init_functions + + + +def test_is_zero_diff_async_sleep(): + original_code = ''' +import time + +async def task(): + time.sleep(1) + return "done" +''' + optimized_code = ''' +import asyncio + +async def task(): + await asyncio.sleep(1) + return "done" +''' + assert not is_zero_diff(original_code, optimized_code) + +def test_is_zero_diff_with_equivalent_code(): + original_code = ''' +import asyncio + +async def task(): + await asyncio.sleep(1) + return "done" +''' + optimized_code = ''' +import asyncio + +async def task(): + """A task that does something.""" + await asyncio.sleep(1) + return "done" +''' + assert is_zero_diff(original_code, optimized_code) \ No newline at end of file diff --git a/tests/test_code_utils.py b/tests/test_code_utils.py index 0ab78d2ef..b17fd0758 100644 --- a/tests/test_code_utils.py +++ b/tests/test_code_utils.py @@ -17,10 +17,10 @@ is_class_defined_in_file, module_name_from_file_path, path_belongs_to_site_packages, - has_any_async_functions, + validate_python_code, ) from codeflash.code_utils.concolic_utils import clean_concolic_tests -from codeflash.code_utils.coverage_utils import generate_candidates, prepare_coverage_files +from codeflash.code_utils.coverage_utils import extract_dependent_function, generate_candidates, prepare_coverage_files @pytest.fixture @@ -308,6 +308,86 @@ def my_function(): assert is_class_defined_in_file("MyClass", test_file) is False +@pytest.fixture +def mock_code_context(): + """Mock CodeOptimizationContext for testing extract_dependent_function.""" + from unittest.mock import MagicMock + from codeflash.models.models import CodeOptimizationContext + + context = MagicMock(spec=CodeOptimizationContext) + context.preexisting_objects = [] + return context + + +def test_extract_dependent_function_sync_and_async(mock_code_context): + """Test extract_dependent_function with both sync and async functions.""" + # Test sync function extraction + mock_code_context.testgen_context_code = """ +def main_function(): + pass + +def helper_function(): + pass +""" + assert extract_dependent_function("main_function", mock_code_context) == "helper_function" + + # Test async function extraction + mock_code_context.testgen_context_code = """ +def main_function(): + pass + +async def async_helper_function(): + pass +""" + assert extract_dependent_function("main_function", mock_code_context) == "async_helper_function" + + +def test_extract_dependent_function_edge_cases(mock_code_context): + """Test extract_dependent_function edge cases.""" + # No dependent functions + mock_code_context.testgen_context_code = """ +def main_function(): + pass +""" + assert extract_dependent_function("main_function", mock_code_context) is False + + # Multiple dependent functions + mock_code_context.testgen_context_code = """ +def main_function(): + pass + +def helper1(): + pass + +async def helper2(): + pass +""" + assert extract_dependent_function("main_function", mock_code_context) is False + + +def test_extract_dependent_function_mixed_scenarios(mock_code_context): + """Test extract_dependent_function with mixed sync/async scenarios.""" + # Async main with sync helper + mock_code_context.testgen_context_code = """ +async def async_main(): + pass + +def sync_helper(): + pass +""" + assert extract_dependent_function("async_main", mock_code_context) == "sync_helper" + + # Only async functions + mock_code_context.testgen_context_code = """ +async def async_main(): + pass + +async def async_helper(): + pass +""" + assert extract_dependent_function("async_main", mock_code_context) == "async_helper" + + def test_is_class_defined_in_file_with_non_existing_file() -> None: non_existing_file = Path("/non/existing/file.py") @@ -445,25 +525,41 @@ def test_Grammar_copy(): assert cleaned_code == expected_cleaned_code.strip() -def test_has_any_async_functions_with_async_code() -> None: - code = """ -def normal_function(): - pass +def test_validate_python_code_valid() -> None: + code = "def hello():\n return 'world'" + result = validate_python_code(code) + assert result == code -async def async_function(): - pass -""" - result = has_any_async_functions(code) - assert result is True +def test_validate_python_code_invalid() -> None: + code = "def hello(:\n return 'world'" + with pytest.raises(ValueError, match="Invalid Python code"): + validate_python_code(code) -def test_has_any_async_functions_without_async_code() -> None: - code = """ -def normal_function(): - pass -def another_function(): - pass +def test_validate_python_code_empty() -> None: + code = "" + result = validate_python_code(code) + assert result == code + + +def test_validate_python_code_complex_invalid() -> None: + code = "if True\n print('missing colon')" + with pytest.raises(ValueError, match="Invalid Python code.*line 1.*column 8"): + validate_python_code(code) + + +def test_validate_python_code_valid_complex() -> None: + code = """ +def calculate(a, b): + if a > b: + return a + b + else: + return a * b + +class MyClass: + def __init__(self): + self.value = 42 """ - result = has_any_async_functions(code) - assert result is False + result = validate_python_code(code) + assert result == code From 08a3412b3c0824468a834f283b6a0f45e2fef76c Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:31:54 -0700 Subject: [PATCH 17/34] some other tests --- tests/test_critic.py | 164 +++++++- tests/test_unused_helper_revert.py | 652 ++++++++++++++++++++++++----- 2 files changed, 719 insertions(+), 97 deletions(-) diff --git a/tests/test_critic.py b/tests/test_critic.py index 27df4dde9..3004c53d0 100644 --- a/tests/test_critic.py +++ b/tests/test_critic.py @@ -14,7 +14,13 @@ TestResults, TestType, ) -from codeflash.result.critic import coverage_critic, performance_gain, quantity_of_tests_critic, speedup_critic +from codeflash.result.critic import ( + coverage_critic, + performance_gain, + quantity_of_tests_critic, + speedup_critic, + throughput_gain, +) def test_performance_gain() -> None: @@ -429,3 +435,159 @@ def test_coverage_critic() -> None: ) assert coverage_critic(unittest_coverage, "unittest") is True + + +def test_throughput_gain() -> None: + """Test throughput_gain calculation.""" + # Test basic throughput improvement + assert throughput_gain(original_throughput=100, optimized_throughput=150) == 0.5 # 50% improvement + + # Test no improvement + assert throughput_gain(original_throughput=100, optimized_throughput=100) == 0.0 + + # Test regression + assert throughput_gain(original_throughput=100, optimized_throughput=80) == -0.2 # 20% regression + + # Test zero original throughput (edge case) + assert throughput_gain(original_throughput=0, optimized_throughput=50) == 0.0 + + # Test large improvement + assert throughput_gain(original_throughput=50, optimized_throughput=200) == 3.0 # 300% improvement + + +def test_speedup_critic_with_async_throughput() -> None: + """Test speedup_critic with async throughput evaluation.""" + original_code_runtime = 10000 # 10 microseconds + original_async_throughput = 100 + + # Test case 1: Both runtime and throughput improve significantly + candidate_result = OptimizedCandidateResult( + max_loop_count=5, + best_test_runtime=8000, # 20% runtime improvement + behavior_test_results=TestResults(), + benchmarking_test_results=TestResults(), + optimization_candidate_index=0, + total_candidate_timing=8000, + async_throughput=120, # 20% throughput improvement + ) + + assert speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=None, + original_async_throughput=original_async_throughput, + best_throughput_until_now=None, + disable_gh_action_noise=True + ) + + # Test case 2: Runtime improves significantly, throughput doesn't meet threshold (should pass) + candidate_result = OptimizedCandidateResult( + max_loop_count=5, + best_test_runtime=8000, # 20% runtime improvement + behavior_test_results=TestResults(), + benchmarking_test_results=TestResults(), + optimization_candidate_index=0, + total_candidate_timing=8000, + async_throughput=105, # Only 5% throughput improvement (below 10% threshold) + ) + + assert speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=None, + original_async_throughput=original_async_throughput, + best_throughput_until_now=None, + disable_gh_action_noise=True + ) + + # Test case 3: Throughput improves significantly, runtime doesn't meet threshold (should pass) + candidate_result = OptimizedCandidateResult( + max_loop_count=5, + best_test_runtime=9800, # Only 2% runtime improvement (below 5% threshold) + behavior_test_results=TestResults(), + benchmarking_test_results=TestResults(), + optimization_candidate_index=0, + total_candidate_timing=9800, + async_throughput=120, # 20% throughput improvement + ) + + assert speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=None, + original_async_throughput=original_async_throughput, + best_throughput_until_now=None, + disable_gh_action_noise=True + ) + + # Test case 4: No throughput data - should fall back to runtime-only evaluation + candidate_result = OptimizedCandidateResult( + max_loop_count=5, + best_test_runtime=8000, # 20% runtime improvement + behavior_test_results=TestResults(), + benchmarking_test_results=TestResults(), + optimization_candidate_index=0, + total_candidate_timing=8000, + async_throughput=None, # No throughput data + ) + + assert speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=None, + original_async_throughput=None, # No original throughput data + best_throughput_until_now=None, + disable_gh_action_noise=True + ) + + # Test case 5: Test best_throughput_until_now comparison + candidate_result = OptimizedCandidateResult( + max_loop_count=5, + best_test_runtime=8000, # 20% runtime improvement + behavior_test_results=TestResults(), + benchmarking_test_results=TestResults(), + optimization_candidate_index=0, + total_candidate_timing=8000, + async_throughput=115, # 15% throughput improvement + ) + + # Should pass when no best throughput yet + assert speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=None, + original_async_throughput=original_async_throughput, + best_throughput_until_now=None, + disable_gh_action_noise=True + ) + + # Should fail when there's a better throughput already + assert not speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=7000, # Better runtime already exists + original_async_throughput=original_async_throughput, + best_throughput_until_now=120, # Better throughput already exists + disable_gh_action_noise=True + ) + + # Test case 6: Zero original throughput (edge case) + candidate_result = OptimizedCandidateResult( + max_loop_count=5, + best_test_runtime=8000, # 20% runtime improvement + behavior_test_results=TestResults(), + benchmarking_test_results=TestResults(), + optimization_candidate_index=0, + total_candidate_timing=8000, + async_throughput=50, + ) + + # Should pass when original throughput is 0 (throughput evaluation skipped) + assert speedup_critic( + candidate_result=candidate_result, + original_code_runtime=original_code_runtime, + best_runtime_until_now=None, + original_async_throughput=0, # Zero original throughput + best_throughput_until_now=None, + disable_gh_action_noise=True + ) diff --git a/tests/test_unused_helper_revert.py b/tests/test_unused_helper_revert.py index c342f0c1b..25909a688 100644 --- a/tests/test_unused_helper_revert.py +++ b/tests/test_unused_helper_revert.py @@ -6,9 +6,11 @@ import pytest from codeflash.context.unused_definition_remover import detect_unused_helper_functions from codeflash.discovery.functions_to_optimize import FunctionToOptimize -from codeflash.models.models import CodeStringsMarkdown, FunctionParent +from codeflash.models.models import CodeStringsMarkdown from codeflash.optimization.function_optimizer import FunctionOptimizer from codeflash.verification.verification_utils import TestConfig +from codeflash.context.unused_definition_remover import revert_unused_helper_functions + @pytest.fixture @@ -225,6 +227,15 @@ def helper_function_2(x): def test_no_unused_helpers_no_revert(temp_project): """Test that when all helpers are still used, nothing is reverted.""" temp_dir, main_file, test_cfg = temp_project + + + # Store original content to verify nothing changes + original_content = main_file.read_text() + + revert_unused_helper_functions(temp_dir, [], {}) + + # Verify the file content remains unchanged + assert main_file.read_text() == original_content, "File should remain unchanged when no helpers to revert" # Optimized version that still calls both helpers optimized_code = """ @@ -308,17 +319,23 @@ def helper_function_1(x): def helper_function_2(x): \"\"\"Second helper function.\"\"\" return x * 3 + +def helper_function_1(y): # Duplicate name to test line 575 + \"\"\"Overloaded helper function.\"\"\" + return y + 10 """) - # Optimized version that only calls one helper + # Optimized version that only calls one helper with aliased import optimized_code = """ ```python:main.py -from helpers import helper_function_1 +from helpers import helper_function_1 as h1 +import helpers as h_module def entrypoint_function(n): - \"\"\"Optimized function that only calls one helper.\"\"\" - result1 = helper_function_1(n) - return result1 + n * 3 # Inlined helper_function_2 + \"\"\"Optimized function that only calls one helper with aliasing.\"\"\" + result1 = h1(n) # Using aliased import + # Inlined helper_function_2 functionality: n * 3 + return result1 + n * 3 # Fully inlined helper_function_2 ``` """ @@ -1462,113 +1479,338 @@ def calculate_class(cls, n): shutil.rmtree(temp_dir, ignore_errors=True) -def test_unused_helper_detection_with_duplicated_function_name_in_different_classes(): - """Test detection when helpers are called via module.function style.""" +def test_async_entrypoint_with_async_helpers(): + """Test that unused async helper functions are correctly detected when entrypoint is async.""" temp_dir = Path(tempfile.mkdtemp()) try: - # Main file + # Main file with async entrypoint and async helpers main_file = temp_dir / "main.py" - main_file.write_text("""from __future__ import annotations -import json -from helpers import replace_quotes_with_backticks, simplify_worktree_paths -from dataclasses import asdict, dataclass - -@dataclass -class LspMessage: - - def serialize(self) -> str: - data = self._loop_through(asdict(self)) - # Important: keep type as the first key, for making it easy and fast for the client to know if this is a lsp message before parsing it - ordered = {"type": self.type(), **data} - return ( - message_delimiter - + json.dumps(ordered) - + message_delimiter + main_file.write_text(""" +async def async_helper_1(x): + \"\"\"First async helper function.\"\"\" + return x * 2 + +async def async_helper_2(x): + \"\"\"Second async helper function.\"\"\" + return x * 3 + +async def async_entrypoint(n): + \"\"\"Async entrypoint function that calls async helpers.\"\"\" + result1 = await async_helper_1(n) + result2 = await async_helper_2(n) + return result1 + result2 +""") + + # Optimized version that only calls one async helper + optimized_code = """ +```python:main.py +async def async_helper_1(x): + \"\"\"First async helper function.\"\"\" + return x * 2 + +async def async_helper_2(x): + \"\"\"Second async helper function - should be unused.\"\"\" + return x * 3 + +async def async_entrypoint(n): + \"\"\"Optimized async entrypoint that only calls one helper.\"\"\" + result1 = await async_helper_1(n) + return result1 + n * 3 # Inlined async_helper_2 +``` +""" + + # Create test config + test_cfg = TestConfig( + tests_root=temp_dir / "tests", + tests_project_rootdir=temp_dir, + project_root_path=temp_dir, + test_framework="pytest", + pytest_cmd="pytest", ) + # Create FunctionToOptimize instance for async function + function_to_optimize = FunctionToOptimize( + file_path=main_file, + function_name="async_entrypoint", + parents=[], + is_async=True + ) -@dataclass -class LspMarkdownMessage(LspMessage): + # Create function optimizer + optimizer = FunctionOptimizer( + function_to_optimize=function_to_optimize, + test_cfg=test_cfg, + function_to_optimize_source_code=main_file.read_text(), + ) + + # Get original code context + ctx_result = optimizer.get_code_optimization_context() + assert ctx_result.is_successful(), f"Failed to get context: {ctx_result.failure()}" + + code_context = ctx_result.unwrap() + + # Test unused helper detection + unused_helpers = detect_unused_helper_functions(optimizer.function_to_optimize, code_context, CodeStringsMarkdown.parse_markdown_code(optimized_code)) + + # Should detect async_helper_2 as unused + unused_names = {uh.qualified_name for uh in unused_helpers} + expected_unused = {"async_helper_2"} + + assert unused_names == expected_unused, f"Expected unused: {expected_unused}, got: {unused_names}" + + finally: + # Cleanup + import shutil + shutil.rmtree(temp_dir, ignore_errors=True) - def serialize(self) -> str: - self.markdown = simplify_worktree_paths(self.markdown) - self.markdown = replace_quotes_with_backticks(self.markdown) - return super().serialize() -""") - # Helpers file - helpers_file = temp_dir / "helpers.py" - helpers_file.write_text("""def simplify_worktree_paths(msg: str, highlight: bool = True) -> str: # noqa: FBT001, FBT002 - path_in_msg = worktree_path_regex.search(msg) - if path_in_msg: - last_part_of_path = path_in_msg.group(0).split("/")[-1] - if highlight: - last_part_of_path = f"`{last_part_of_path}`" - return msg.replace(path_in_msg.group(0), last_part_of_path) - return msg - - -def replace_quotes_with_backticks(text: str) -> str: - # double-quoted strings - text = _double_quote_pat.sub(r"`\1`", text) - # single-quoted strings - return _single_quote_pat.sub(r"`\1`", text) +def test_sync_entrypoint_with_async_helpers(): + """Test that unused async helper functions are detected when entrypoint is sync.""" + temp_dir = Path(tempfile.mkdtemp()) + + try: + # Main file with sync entrypoint and async helpers + main_file = temp_dir / "main.py" + main_file.write_text(""" +import asyncio + +async def async_helper_1(x): + \"\"\"First async helper function.\"\"\" + return x * 2 + +async def async_helper_2(x): + \"\"\"Second async helper function.\"\"\" + return x * 3 + +def sync_entrypoint(n): + \"\"\"Sync entrypoint function that calls async helpers.\"\"\" + result1 = asyncio.run(async_helper_1(n)) + result2 = asyncio.run(async_helper_2(n)) + return result1 + result2 """) - # Optimized version that only uses add_numbers + # Optimized version that only calls one async helper optimized_code = """ ```python:main.py -from __future__ import annotations +import asyncio -import json -from dataclasses import asdict, dataclass +async def async_helper_1(x): + \"\"\"First async helper function.\"\"\" + return x * 2 + +async def async_helper_2(x): + \"\"\"Second async helper function - should be unused.\"\"\" + return x * 3 -from codeflash.lsp.helpers import (replace_quotes_with_backticks, - simplify_worktree_paths) +def sync_entrypoint(n): + \"\"\"Optimized sync entrypoint that only calls one async helper.\"\"\" + result1 = asyncio.run(async_helper_1(n)) + return result1 + n * 3 # Inlined async_helper_2 +``` +""" + # Create test config + test_cfg = TestConfig( + tests_root=temp_dir / "tests", + tests_project_rootdir=temp_dir, + project_root_path=temp_dir, + test_framework="pytest", + pytest_cmd="pytest", + ) -@dataclass -class LspMessage: + # Create FunctionToOptimize instance for sync function + function_to_optimize = FunctionToOptimize( + file_path=main_file, + function_name="sync_entrypoint", + parents=[] + ) - def serialize(self) -> str: - # Use local variable to minimize lookup costs and avoid unnecessary dictionary unpacking - data = self._loop_through(asdict(self)) - msg_type = self.type() - ordered = {'type': msg_type} - ordered.update(data) - return ( - message_delimiter - + json.dumps(ordered) - + message_delimiter # \u241F is the message delimiter becuase it can be more than one message sent over the same message, so we need something to separate each message + # Create function optimizer + optimizer = FunctionOptimizer( + function_to_optimize=function_to_optimize, + test_cfg=test_cfg, + function_to_optimize_source_code=main_file.read_text(), ) -@dataclass -class LspMarkdownMessage(LspMessage): + # Get original code context + ctx_result = optimizer.get_code_optimization_context() + assert ctx_result.is_successful(), f"Failed to get context: {ctx_result.failure()}" + + code_context = ctx_result.unwrap() + + # Test unused helper detection + unused_helpers = detect_unused_helper_functions(optimizer.function_to_optimize, code_context, CodeStringsMarkdown.parse_markdown_code(optimized_code)) + + # Should detect async_helper_2 as unused + unused_names = {uh.qualified_name for uh in unused_helpers} + expected_unused = {"async_helper_2"} + + assert unused_names == expected_unused, f"Expected unused: {expected_unused}, got: {unused_names}" + + finally: + # Cleanup + import shutil + shutil.rmtree(temp_dir, ignore_errors=True) + + +def test_mixed_sync_and_async_helpers(): + """Test detection when both sync and async helpers are mixed.""" + temp_dir = Path(tempfile.mkdtemp()) + + try: + # Main file with mixed sync and async helpers + main_file = temp_dir / "main.py" + main_file.write_text(""" +import asyncio + +def sync_helper_1(x): + \"\"\"Sync helper function.\"\"\" + return x * 2 + +async def async_helper_1(x): + \"\"\"Async helper function.\"\"\" + return x * 3 + +def sync_helper_2(x): + \"\"\"Another sync helper function.\"\"\" + return x * 4 + +async def async_helper_2(x): + \"\"\"Another async helper function.\"\"\" + return x * 5 + +async def mixed_entrypoint(n): + \"\"\"Async entrypoint function that calls both sync and async helpers.\"\"\" + sync_result = sync_helper_1(n) + async_result = await async_helper_1(n) + sync_result2 = sync_helper_2(n) + async_result2 = await async_helper_2(n) + return sync_result + async_result + sync_result2 + async_result2 +""") - def serialize(self) -> str: - # Side effect required, must preserve for behavioral correctness - self.markdown = simplify_worktree_paths(self.markdown) - self.markdown = replace_quotes_with_backticks(self.markdown) - return super().serialize() + # Optimized version that only calls some helpers + optimized_code = """ +```python:main.py +import asyncio + +def sync_helper_1(x): + \"\"\"Sync helper function.\"\"\" + return x * 2 + +async def async_helper_1(x): + \"\"\"Async helper function.\"\"\" + return x * 3 + +def sync_helper_2(x): + \"\"\"Another sync helper function - should be unused.\"\"\" + return x * 4 + +async def async_helper_2(x): + \"\"\"Another async helper function - should be unused.\"\"\" + return x * 5 + +async def mixed_entrypoint(n): + \"\"\"Optimized async entrypoint that only calls some helpers.\"\"\" + sync_result = sync_helper_1(n) + async_result = await async_helper_1(n) + return sync_result + async_result + n * 4 + n * 5 # Inlined both helper_2 functions ``` -```python:helpers.py -def simplify_worktree_paths(msg: str, highlight: bool = True) -> str: # noqa: FBT001, FBT002 - m = worktree_path_regex.search(msg) - if m: - # More efficient way to get last path part - last_part_of_path = m.group(0).rpartition('/')[-1] - if highlight: - last_part_of_path = f"`{last_part_of_path}`" - return msg.replace(m.group(0), last_part_of_path) - return msg - -def replace_quotes_with_backticks(text: str) -> str: - # Efficient string substitution, reduces intermediate string allocations - return _single_quote_pat.sub( - r"`\1`", - _double_quote_pat.sub(r"`\1`", text), - ) +""" + + # Create test config + test_cfg = TestConfig( + tests_root=temp_dir / "tests", + tests_project_rootdir=temp_dir, + project_root_path=temp_dir, + test_framework="pytest", + pytest_cmd="pytest", + ) + + # Create FunctionToOptimize instance for async function + function_to_optimize = FunctionToOptimize( + file_path=main_file, + function_name="mixed_entrypoint", + parents=[], + is_async=True + ) + + # Create function optimizer + optimizer = FunctionOptimizer( + function_to_optimize=function_to_optimize, + test_cfg=test_cfg, + function_to_optimize_source_code=main_file.read_text(), + ) + + # Get original code context + ctx_result = optimizer.get_code_optimization_context() + assert ctx_result.is_successful(), f"Failed to get context: {ctx_result.failure()}" + + code_context = ctx_result.unwrap() + + # Test unused helper detection + unused_helpers = detect_unused_helper_functions(optimizer.function_to_optimize, code_context, CodeStringsMarkdown.parse_markdown_code(optimized_code)) + + # Should detect both sync_helper_2 and async_helper_2 as unused + unused_names = {uh.qualified_name for uh in unused_helpers} + expected_unused = {"sync_helper_2", "async_helper_2"} + + assert unused_names == expected_unused, f"Expected unused: {expected_unused}, got: {unused_names}" + + finally: + # Cleanup + import shutil + shutil.rmtree(temp_dir, ignore_errors=True) + + +def test_async_class_methods(): + """Test unused async method detection in classes.""" + temp_dir = Path(tempfile.mkdtemp()) + + try: + # Main file with class containing async methods + main_file = temp_dir / "main.py" + main_file.write_text(""" +class AsyncProcessor: + async def entrypoint_method(self, n): + \"\"\"Async main method that calls async helper methods.\"\"\" + result1 = await self.async_helper_method_1(n) + result2 = await self.async_helper_method_2(n) + return result1 + result2 + + async def async_helper_method_1(self, x): + \"\"\"First async helper method.\"\"\" + return x * 2 + + async def async_helper_method_2(self, x): + \"\"\"Second async helper method.\"\"\" + return x * 3 + + def sync_helper_method(self, x): + \"\"\"Sync helper method.\"\"\" + return x * 4 +""") + + # Optimized version that only calls one async helper + optimized_code = """ +```python:main.py +class AsyncProcessor: + async def entrypoint_method(self, n): + \"\"\"Optimized async method that only calls one helper.\"\"\" + result1 = await self.async_helper_method_1(n) + return result1 + n * 3 # Inlined async_helper_method_2 + + async def async_helper_method_1(self, x): + \"\"\"First async helper method.\"\"\" + return x * 2 + + async def async_helper_method_2(self, x): + \"\"\"Second async helper method - should be unused.\"\"\" + return x * 3 + + def sync_helper_method(self, x): + \"\"\"Sync helper method - should be unused.\"\"\" + return x * 4 ``` """ @@ -1581,31 +1823,249 @@ def replace_quotes_with_backticks(text: str) -> str: pytest_cmd="pytest", ) - # Create FunctionToOptimize instance + # Create FunctionToOptimize instance for async class method + from codeflash.models.models import FunctionParent + function_to_optimize = FunctionToOptimize( - file_path=main_file, function_name="serialize", qualified_name="serialize", parents=[ - FunctionParent(name="LspMarkdownMessage", type="ClassDef"), - ] + file_path=main_file, + function_name="entrypoint_method", + parents=[FunctionParent(name="AsyncProcessor", type="ClassDef")], + is_async=True ) + # Create function optimizer optimizer = FunctionOptimizer( function_to_optimize=function_to_optimize, test_cfg=test_cfg, function_to_optimize_source_code=main_file.read_text(), ) + # Get original code context ctx_result = optimizer.get_code_optimization_context() assert ctx_result.is_successful(), f"Failed to get context: {ctx_result.failure()}" code_context = ctx_result.unwrap() + # Test unused helper detection unused_helpers = detect_unused_helper_functions(optimizer.function_to_optimize, code_context, CodeStringsMarkdown.parse_markdown_code(optimized_code)) + # Should detect async_helper_method_2 as unused (sync_helper_method may not be discovered as helper) unused_names = {uh.qualified_name for uh in unused_helpers} - assert len(unused_names) == 0 # no unused helpers + expected_unused = {"AsyncProcessor.async_helper_method_2"} + + assert unused_names == expected_unused, f"Expected unused: {expected_unused}, got: {unused_names}" finally: # Cleanup import shutil + shutil.rmtree(temp_dir, ignore_errors=True) + +def test_async_helper_revert_functionality(): + """Test that unused async helper functions are correctly reverted to original definitions.""" + temp_dir = Path(tempfile.mkdtemp()) + + try: + # Main file with async functions + main_file = temp_dir / "main.py" + main_file.write_text(""" +async def async_helper_1(x): + \"\"\"First async helper function.\"\"\" + return x * 2 + +async def async_helper_2(x): + \"\"\"Second async helper function.\"\"\" + return x * 3 + +async def async_entrypoint(n): + \"\"\"Async entrypoint function that calls async helpers.\"\"\" + result1 = await async_helper_1(n) + result2 = await async_helper_2(n) + return result1 + result2 +""") + + # Optimized version that only calls one helper and modifies the unused one + optimized_code = """ +```python:main.py +async def async_helper_1(x): + \"\"\"First async helper function.\"\"\" + return x * 2 + +async def async_helper_2(x): + \"\"\"Modified async helper function - should be reverted.\"\"\" + return x * 10 # This change should be reverted + +async def async_entrypoint(n): + \"\"\"Optimized async entrypoint that only calls one helper.\"\"\" + result1 = await async_helper_1(n) + return result1 + n * 3 # Inlined async_helper_2 +``` +""" + + # Create test config + test_cfg = TestConfig( + tests_root=temp_dir / "tests", + tests_project_rootdir=temp_dir, + project_root_path=temp_dir, + test_framework="pytest", + pytest_cmd="pytest", + ) + + # Create FunctionToOptimize instance for async function + function_to_optimize = FunctionToOptimize( + file_path=main_file, + function_name="async_entrypoint", + parents=[], + is_async=True + ) + + # Create function optimizer + optimizer = FunctionOptimizer( + function_to_optimize=function_to_optimize, + test_cfg=test_cfg, + function_to_optimize_source_code=main_file.read_text(), + ) + + # Get original code context + ctx_result = optimizer.get_code_optimization_context() + assert ctx_result.is_successful(), f"Failed to get context: {ctx_result.failure()}" + + code_context = ctx_result.unwrap() + + # Store original helper code + original_helper_code = {main_file: main_file.read_text()} + + # Apply optimization and test reversion + optimizer.replace_function_and_helpers_with_optimized_code( + code_context, CodeStringsMarkdown.parse_markdown_code(optimized_code), original_helper_code + ) + + # Check final file content + final_content = main_file.read_text() + + # The entrypoint should be optimized + assert "result1 + n * 3" in final_content, "Async entrypoint function should be optimized" + + # async_helper_2 should be reverted to original (return x * 3, not x * 10) + assert "return x * 3" in final_content, "async_helper_2 should be reverted to original" + assert "return x * 10" not in final_content, "async_helper_2 should not contain the modified version" + + # async_helper_1 should remain (it's still called) + assert "async def async_helper_1(x):" in final_content, "async_helper_1 should still exist" + + finally: + # Cleanup + import shutil + shutil.rmtree(temp_dir, ignore_errors=True) + + +def test_async_generators_and_coroutines(): + """Test detection with async generators and coroutines.""" + temp_dir = Path(tempfile.mkdtemp()) + + try: + # Main file with async generators and coroutines + main_file = temp_dir / "main.py" + main_file.write_text(""" +import asyncio + +async def async_generator_helper(n): + \"\"\"Async generator helper.\"\"\" + for i in range(n): + yield i * 2 + +async def coroutine_helper(x): + \"\"\"Coroutine helper.\"\"\" + await asyncio.sleep(0.1) + return x * 3 + +async def another_coroutine_helper(x): + \"\"\"Another coroutine helper.\"\"\" + await asyncio.sleep(0.1) + return x * 4 + +async def async_entrypoint_with_generators(n): + \"\"\"Async entrypoint function that uses generators and coroutines.\"\"\" + results = [] + async for value in async_generator_helper(n): + results.append(value) + + final_result = await coroutine_helper(sum(results)) + another_result = await another_coroutine_helper(n) + return final_result + another_result +""") + + # Optimized version that doesn't use one of the coroutines + optimized_code = """ +```python:main.py +import asyncio + +async def async_generator_helper(n): + \"\"\"Async generator helper.\"\"\" + for i in range(n): + yield i * 2 + +async def coroutine_helper(x): + \"\"\"Coroutine helper.\"\"\" + await asyncio.sleep(0.1) + return x * 3 + +async def another_coroutine_helper(x): + \"\"\"Another coroutine helper - should be unused.\"\"\" + await asyncio.sleep(0.1) + return x * 4 + +async def async_entrypoint_with_generators(n): + \"\"\"Optimized async entrypoint that inlines one coroutine.\"\"\" + results = [] + async for value in async_generator_helper(n): + results.append(value) + + final_result = await coroutine_helper(sum(results)) + return final_result + n * 4 # Inlined another_coroutine_helper +``` +""" + + # Create test config + test_cfg = TestConfig( + tests_root=temp_dir / "tests", + tests_project_rootdir=temp_dir, + project_root_path=temp_dir, + test_framework="pytest", + pytest_cmd="pytest", + ) + + # Create FunctionToOptimize instance for async function + function_to_optimize = FunctionToOptimize( + file_path=main_file, + function_name="async_entrypoint_with_generators", + parents=[], + is_async=True + ) + + # Create function optimizer + optimizer = FunctionOptimizer( + function_to_optimize=function_to_optimize, + test_cfg=test_cfg, + function_to_optimize_source_code=main_file.read_text(), + ) + + # Get original code context + ctx_result = optimizer.get_code_optimization_context() + assert ctx_result.is_successful(), f"Failed to get context: {ctx_result.failure()}" + + code_context = ctx_result.unwrap() + + # Test unused helper detection + unused_helpers = detect_unused_helper_functions(optimizer.function_to_optimize, code_context, CodeStringsMarkdown.parse_markdown_code(optimized_code)) + + # Should detect another_coroutine_helper as unused + unused_names = {uh.qualified_name for uh in unused_helpers} + expected_unused = {"another_coroutine_helper"} + + assert unused_names == expected_unused, f"Expected unused: {expected_unused}, got: {unused_names}" + + finally: + # Cleanup + import shutil shutil.rmtree(temp_dir, ignore_errors=True) From ce1445c5df7fe99508540445f1b534cacb94dd10 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:39:59 -0700 Subject: [PATCH 18/34] Update function_optimizer.py --- codeflash/optimization/function_optimizer.py | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index b1df6fe2d..af654941b 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -593,6 +593,18 @@ def determine_best_candidate( ) tree.add(f"Speedup percentage: {perf_gain * 100:.1f}%") tree.add(f"Speedup ratio: {perf_gain + 1:.3f}X") + if ( + original_code_baseline.async_throughput is not None + and candidate_result.async_throughput is not None + ): + from codeflash.result.critic import throughput_gain + throughput_gain_value = throughput_gain( + original_throughput=original_code_baseline.async_throughput, + optimized_throughput=candidate_result.async_throughput, + ) + tree.add(f"Original async throughput: {original_code_baseline.async_throughput} executions") + tree.add(f"Optimized async throughput: {candidate_result.async_throughput} executions") + tree.add(f"Throughput improvement: {throughput_gain_value * 100:.1f}%") line_profile_test_results = self.line_profiler_step( code_context=code_context, original_helper_code=original_helper_code, @@ -628,6 +640,7 @@ def determine_best_candidate( replay_performance_gain=replay_perf_gain if self.args.benchmark else None, winning_benchmarking_test_results=candidate_result.benchmarking_test_results, winning_replay_benchmarking_test_results=candidate_result.benchmarking_test_results, + async_throughput=candidate_result.async_throughput, ) valid_optimizations.append(best_optimization) # queue corresponding refined optimization for best optimization @@ -652,6 +665,16 @@ def determine_best_candidate( ) tree.add(f"Speedup percentage: {perf_gain * 100:.1f}%") tree.add(f"Speedup ratio: {perf_gain + 1:.3f}X") + if ( + original_code_baseline.async_throughput is not None + and candidate_result.async_throughput is not None + ): + from codeflash.result.critic import throughput_gain + throughput_gain_value = throughput_gain( + original_throughput=original_code_baseline.async_throughput, + optimized_throughput=candidate_result.async_throughput, + ) + tree.add(f"Throughput gain: {throughput_gain_value * 100:.1f}%") if is_LSP_enabled(): lsp_log(LspMarkdownMessage(markdown=tree_to_markdown(tree))) @@ -695,6 +718,7 @@ def determine_best_candidate( replay_performance_gain=valid_opt.replay_performance_gain, winning_benchmarking_test_results=valid_opt.winning_benchmarking_test_results, winning_replay_benchmarking_test_results=valid_opt.winning_replay_benchmarking_test_results, + async_throughput=valid_opt.async_throughput, ) valid_candidates_with_shorter_code.append(new_best_opt) diff_lens_list.append( From 58f088cc36750c4f5caf0348c510878565b57034 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 16:55:00 -0700 Subject: [PATCH 19/34] throughput --- codeflash/optimization/function_optimizer.py | 66 +++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index af654941b..13158e470 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -84,14 +84,20 @@ TestType, ) from codeflash.result.create_pr import check_create_pr, existing_tests_source_for -from codeflash.result.critic import coverage_critic, performance_gain, quantity_of_tests_critic, speedup_critic +from codeflash.result.critic import ( + coverage_critic, + performance_gain, + quantity_of_tests_critic, + speedup_critic, + throughput_gain, +) from codeflash.result.explanation import Explanation from codeflash.telemetry.posthog_cf import ph from codeflash.verification.concolic_testing import generate_concolic_tests from codeflash.verification.equivalence import compare_test_results from codeflash.verification.instrument_codeflash_capture import instrument_codeflash_capture from codeflash.verification.parse_line_profile_test_output import parse_line_profile_results -from codeflash.verification.parse_test_output import parse_test_results +from codeflash.verification.parse_test_output import calculate_function_throughput_from_test_results, parse_test_results from codeflash.verification.test_runner import run_behavioral_tests, run_benchmarking_tests, run_line_profile_tests from codeflash.verification.verification_utils import get_test_file_path from codeflash.verification.verifier import generate_tests @@ -582,7 +588,11 @@ def determine_best_candidate( tree = Tree(f"Candidate #{candidate_index} - Runtime Information ⌛") benchmark_tree = None if speedup_critic( - candidate_result, original_code_baseline.runtime, best_runtime_until_now=None + candidate_result, + original_code_baseline.runtime, + best_runtime_until_now=None, + original_async_throughput=original_code_baseline.async_throughput, + best_throughput_until_now=None, ) and quantity_of_tests_critic(candidate_result): tree.add("This candidate is faster than the original code. 🚀") # TODO: Change this description tree.add(f"Original summed runtime: {humanize_runtime(original_code_baseline.runtime)}") @@ -597,7 +607,6 @@ def determine_best_candidate( original_code_baseline.async_throughput is not None and candidate_result.async_throughput is not None ): - from codeflash.result.critic import throughput_gain throughput_gain_value = throughput_gain( original_throughput=original_code_baseline.async_throughput, optimized_throughput=candidate_result.async_throughput, @@ -669,7 +678,6 @@ def determine_best_candidate( original_code_baseline.async_throughput is not None and candidate_result.async_throughput is not None ): - from codeflash.result.critic import throughput_gain throughput_gain_value = throughput_gain( original_throughput=original_code_baseline.async_throughput, optimized_throughput=candidate_result.async_throughput, @@ -1301,6 +1309,8 @@ def find_and_process_best_optimization( function_name=function_to_optimize_qualified_name, file_path=self.function_to_optimize.file_path, benchmark_details=processed_benchmark_info.benchmark_details if processed_benchmark_info else None, + original_async_throughput=original_code_baseline.async_throughput, + best_async_throughput=best_optimization.async_throughput, ) self.replace_function_and_helpers_with_optimized_code( @@ -1383,6 +1393,23 @@ def process_review( original_runtimes_all=original_runtime_by_test, optimized_runtimes_all=optimized_runtime_by_test, ) + original_throughput_str = None + optimized_throughput_str = None + throughput_improvement_str = None + + if ( + self.function_to_optimize.is_async + and original_code_baseline.async_throughput is not None + and best_optimization.async_throughput is not None + ): + original_throughput_str = f"{original_code_baseline.async_throughput} operations/second" + optimized_throughput_str = f"{best_optimization.async_throughput} operations/second" + throughput_improvement_value = throughput_gain( + original_throughput=original_code_baseline.async_throughput, + optimized_throughput=best_optimization.async_throughput, + ) + throughput_improvement_str = f"{throughput_improvement_value * 100:.1f}%" + new_explanation_raw_str = self.aiservice_client.get_new_explanation( source_code=code_context.read_writable_code.flat, dependency_code=code_context.read_only_context_code, @@ -1396,6 +1423,9 @@ def process_review( annotated_tests=generated_tests_str, optimization_id=best_optimization.candidate.optimization_id, original_explanation=best_optimization.candidate.explanation, + original_throughput=original_throughput_str, + optimized_throughput=optimized_throughput_str, + throughput_improvement=throughput_improvement_str, ) new_explanation = Explanation( raw_explanation_message=new_explanation_raw_str or explanation.raw_explanation_message, @@ -1406,6 +1436,8 @@ def process_review( function_name=explanation.function_name, file_path=explanation.file_path, benchmark_details=explanation.benchmark_details, + original_async_throughput=explanation.original_async_throughput, + best_async_throughput=explanation.best_async_throughput, ) self.log_successful_optimization(new_explanation, generated_tests, exp_type) @@ -1633,6 +1665,14 @@ def establish_original_code_baseline( console.rule() logger.debug(f"Total original code runtime (ns): {total_timing}") + async_throughput = None + if self.function_to_optimize.is_async: + async_throughput = calculate_function_throughput_from_test_results( + benchmarking_results, self.function_to_optimize.function_name + ) + logger.debug(f"Original async function throughput: {async_throughput} calls/second") + console.rule() + if self.args.benchmark: replay_benchmarking_test_results = benchmarking_results.group_by_benchmarks( self.total_benchmark_timings.keys(), self.replay_tests_dir, self.project_root @@ -1646,6 +1686,7 @@ def establish_original_code_baseline( runtime=total_timing, coverage_results=coverage_results, line_profile_results=line_profile_results, + async_throughput=async_throughput, ), functions_to_remove, ) @@ -1792,6 +1833,14 @@ def run_optimized_candidate( console.rule() logger.debug(f"Total optimized code {optimization_candidate_index} runtime (ns): {total_candidate_timing}") + + candidate_async_throughput = None + if self.function_to_optimize.is_async: + candidate_async_throughput = calculate_function_throughput_from_test_results( + candidate_benchmarking_results, self.function_to_optimize.function_name + ) + logger.debug(f"Candidate async function throughput: {candidate_async_throughput} calls/second") + if self.args.benchmark: candidate_replay_benchmarking_results = candidate_benchmarking_results.group_by_benchmarks( self.total_benchmark_timings.keys(), self.replay_tests_dir, self.project_root @@ -1811,6 +1860,7 @@ def run_optimized_candidate( else None, optimization_candidate_index=optimization_candidate_index, total_candidate_timing=total_candidate_timing, + async_throughput=candidate_async_throughput, ) ) @@ -1902,8 +1952,10 @@ def run_and_parse_tests( coverage_database_file=coverage_database_file, coverage_config_file=coverage_config_file, ) - else: - results, coverage_results = parse_line_profile_results(line_profiler_output_file=line_profiler_output_file) + if testing_type == TestingMode.PERFORMANCE: + results.perf_stdout = run_result.stdout + return results, coverage_results + results, coverage_results = parse_line_profile_results(line_profiler_output_file=line_profiler_output_file) return results, coverage_results def submit_test_generation_tasks( From 3f1f8d308008a5bef2df5bd4da9a85e8c4a00a41 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 17:06:38 -0700 Subject: [PATCH 20/34] sync with main --- tests/test_code_replacement.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_code_replacement.py b/tests/test_code_replacement.py index 37c64c4da..b04a5d64b 100644 --- a/tests/test_code_replacement.py +++ b/tests/test_code_replacement.py @@ -1708,7 +1708,6 @@ def new_function2(value): """ expected_code = """import numpy as np -print("Hello world") a=2 print("Hello world") def some_fn(): @@ -1784,7 +1783,6 @@ def new_function2(value): """ expected_code = """import numpy as np -print("Hello world") print("Hello world") def some_fn(): a=np.zeros(10) @@ -1863,7 +1861,6 @@ def new_function2(value): """ expected_code = """import numpy as np -print("Hello world") a=3 print("Hello world") def some_fn(): @@ -1941,7 +1938,6 @@ def new_function2(value): """ expected_code = """import numpy as np -print("Hello world") a=2 print("Hello world") def some_fn(): @@ -2020,7 +2016,6 @@ def new_function2(value): """ expected_code = """import numpy as np -print("Hello world") a=3 print("Hello world") def some_fn(): @@ -2107,7 +2102,6 @@ def new_function2(value): a = 6 -print("Hello world") if 2<3: a=4 else: From 4b37f273265794f67049c709a739cd6e88391987 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 17:07:22 -0700 Subject: [PATCH 21/34] linting --- codeflash/context/code_context_extractor.py | 8 ++++++-- codeflash/optimization/function_optimizer.py | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/codeflash/context/code_context_extractor.py b/codeflash/context/code_context_extractor.py index 4579e6b9b..76174877a 100644 --- a/codeflash/context/code_context_extractor.py +++ b/codeflash/context/code_context_extractor.py @@ -334,7 +334,9 @@ def extract_code_markdown_context_from_files( helpers_of_fto.get(file_path, set()) | helpers_of_helpers.get(file_path, set()) ), ) - code_string_context = CodeString(code=code_context, file_path=file_path.resolve().relative_to(project_root_path.resolve())) + code_string_context = CodeString( + code=code_context, file_path=file_path.resolve().relative_to(project_root_path.resolve()) + ) code_context_markdown.code_strings.append(code_string_context) # Extract code from file paths containing helpers of helpers for file_path, helper_function_sources in helpers_of_helpers_no_overlap.items(): @@ -365,7 +367,9 @@ def extract_code_markdown_context_from_files( project_root=project_root_path, helper_functions=list(helpers_of_helpers_no_overlap.get(file_path, set())), ) - code_string_context = CodeString(code=code_context, file_path=file_path.resolve().relative_to(project_root_path.resolve())) + code_string_context = CodeString( + code=code_context, file_path=file_path.resolve().relative_to(project_root_path.resolve()) + ) code_context_markdown.code_strings.append(code_string_context) return code_context_markdown diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index 13158e470..17026fafc 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -1529,9 +1529,7 @@ def establish_original_code_baseline( test_env = self.get_test_env(codeflash_loop_index=0, codeflash_test_iteration=0, codeflash_tracer_disable=1) if self.function_to_optimize.is_async: - from codeflash.code_utils.instrument_existing_tests import ( - instrument_source_module_with_async_decorators, - ) + from codeflash.code_utils.instrument_existing_tests import instrument_source_module_with_async_decorators success, instrumented_source = instrument_source_module_with_async_decorators( self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.BEHAVIOR From 6795a5c862e44d3861c4a25fae637ed2179746d5 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 17:09:39 -0700 Subject: [PATCH 22/34] pass mypy --- codeflash/result/critic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeflash/result/critic.py b/codeflash/result/critic.py index d0ff62176..141a2da19 100644 --- a/codeflash/result/critic.py +++ b/codeflash/result/critic.py @@ -10,7 +10,7 @@ MIN_TESTCASE_PASSED_THRESHOLD, MIN_THROUGHPUT_IMPROVEMENT_THRESHOLD, ) -from codeflash.models.models import TestType +from codeflash.models import models if TYPE_CHECKING: from codeflash.models.models import CoverageData, OptimizedCandidateResult, OriginalCodeBaseline @@ -106,7 +106,7 @@ def quantity_of_tests_critic(candidate_result: OptimizedCandidateResult | Origin if pass_count >= MIN_TESTCASE_PASSED_THRESHOLD: return True # If one or more tests passed, check if least one of them was a successful REPLAY_TEST - return bool(pass_count >= 1 and report[TestType.REPLAY_TEST]["passed"] >= 1) + return bool(pass_count >= 1 and report[models.TestType.REPLAY_TEST]["passed"] >= 1) def coverage_critic(original_code_coverage: CoverageData | None, test_framework: str) -> bool: From 02f96e778f0dd33c23647d627c31bc98a644dc84 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 17:26:54 -0700 Subject: [PATCH 23/34] mypy --- codeflash/result/critic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codeflash/result/critic.py b/codeflash/result/critic.py index 141a2da19..7138d46b8 100644 --- a/codeflash/result/critic.py +++ b/codeflash/result/critic.py @@ -106,7 +106,7 @@ def quantity_of_tests_critic(candidate_result: OptimizedCandidateResult | Origin if pass_count >= MIN_TESTCASE_PASSED_THRESHOLD: return True # If one or more tests passed, check if least one of them was a successful REPLAY_TEST - return bool(pass_count >= 1 and report[models.TestType.REPLAY_TEST]["passed"] >= 1) + return bool(pass_count >= 1 and report[models.TestType.REPLAY_TEST]["passed"] >= 1) # type: ignore # noqa: PGH003 def coverage_critic(original_code_coverage: CoverageData | None, test_framework: str) -> bool: From 2c97b9258d8813c416f9978179fb05dae31b8196 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 17:28:54 -0700 Subject: [PATCH 24/34] formatting --- codeflash/result/critic.py | 2 +- codeflash/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codeflash/result/critic.py b/codeflash/result/critic.py index 7138d46b8..af2f5b503 100644 --- a/codeflash/result/critic.py +++ b/codeflash/result/critic.py @@ -106,7 +106,7 @@ def quantity_of_tests_critic(candidate_result: OptimizedCandidateResult | Origin if pass_count >= MIN_TESTCASE_PASSED_THRESHOLD: return True # If one or more tests passed, check if least one of them was a successful REPLAY_TEST - return bool(pass_count >= 1 and report[models.TestType.REPLAY_TEST]["passed"] >= 1) # type: ignore # noqa: PGH003 + return bool(pass_count >= 1 and report[models.TestType.REPLAY_TEST]["passed"] >= 1) # type: ignore # noqa: PGH003 def coverage_critic(original_code_coverage: CoverageData | None, test_framework: str) -> bool: diff --git a/codeflash/version.py b/codeflash/version.py index bf7c8a1b5..a9633b5f1 100644 --- a/codeflash/version.py +++ b/codeflash/version.py @@ -1,2 +1,2 @@ # These version placeholders will be replaced by uv-dynamic-versioning during build. -__version__ = "0.16.7.post46.dev0+444ff121" +__version__ = "0.16.7.post77.dev0+02f96e77" From b9c4781a592989d58b03938f96a41efafb0c4b56 Mon Sep 17 00:00:00 2001 From: "codeflash-ai[bot]" <148906541+codeflash-ai[bot]@users.noreply.github.com> Date: Sat, 27 Sep 2025 01:33:21 +0000 Subject: [PATCH 25/34] Optimize remove_functions_from_generated_tests The optimization achieves a 22% speedup by eliminating redundant regex compilation and reducing unnecessary string operations. **Key optimizations:** 1. **Pre-compiled regex patterns**: The original code compiled the same regex pattern multiple times (3,114 compilations taking 43.4% of total time). The optimized version compiles each pattern only once upfront using `_compile_function_patterns()`, moving this expensive operation outside the nested loops. 2. **Efficient string manipulation**: Instead of using `re.sub()` which searches the entire string again, the optimized version uses `finditer()` to get match positions directly, then performs string slicing (`source[:start] + source[end:]`) to remove matched functions. This avoids the overhead of regex substitution. 3. **Early termination**: After finding and removing a function match, the code breaks from the inner loop since only one match per function is expected, preventing unnecessary continued iteration. **Performance impact by test case:** - The optimizations are most effective for scenarios with multiple test functions to remove across multiple generated tests (the typical use case) - For edge cases like empty test lists, there's minimal overhead from pre-compilation but no significant benefit - The approach maintains correct behavior for decorated functions (skipping `@pytest.mark.parametrize` functions as intended) The line profiler shows the regex compilation time dropped from 43.4% to being absorbed into the 89.8% upfront compilation cost, while the substitution overhead (51.7% in original) is eliminated entirely. --- codeflash/code_utils/edit_generated_tests.py | 48 ++++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/codeflash/code_utils/edit_generated_tests.py b/codeflash/code_utils/edit_generated_tests.py index 8e50b1d71..16fabefe3 100644 --- a/codeflash/code_utils/edit_generated_tests.py +++ b/codeflash/code_utils/edit_generated_tests.py @@ -207,23 +207,41 @@ def add_runtime_comments_to_generated_tests( def remove_functions_from_generated_tests( generated_tests: GeneratedTestsList, test_functions_to_remove: list[str] ) -> GeneratedTestsList: + # Pre-compile patterns for all function names to remove + function_patterns = _compile_function_patterns(test_functions_to_remove) new_generated_tests = [] - for generated_test in generated_tests.generated_tests: - for test_function in test_functions_to_remove: - function_pattern = re.compile( - rf"(@pytest\.mark\.parametrize\(.*?\)\s*)?(async\s+)?def\s+{re.escape(test_function)}\(.*?\):.*?(?=\n(async\s+)?def\s|$)", - re.DOTALL, - ) - - match = function_pattern.search(generated_test.generated_original_test_source) - - if match is None or "@pytest.mark.parametrize" in match.group(0): - continue - - generated_test.generated_original_test_source = function_pattern.sub( - "", generated_test.generated_original_test_source - ) + for generated_test in generated_tests.generated_tests: + source = generated_test.generated_original_test_source + + # Apply all patterns without redundant searches + for pattern in function_patterns: + # Use finditer and sub only if necessary to avoid unnecessary .search()/.sub() calls + for match in pattern.finditer(source): + # Skip if "@pytest.mark.parametrize" present + # Only the matched function's code is targeted + if "@pytest.mark.parametrize" in match.group(0): + continue + # Remove function from source + # If match, remove the function by substitution in the source + # Replace using start/end indices for efficiency + start, end = match.span() + source = source[:start] + source[end:] + # After removal, break since .finditer() is from left to right, and only one match expected per function in source + break + + generated_test.generated_original_test_source = source new_generated_tests.append(generated_test) return GeneratedTestsList(generated_tests=new_generated_tests) + + +# Pre-compile all function removal regexes upfront for efficiency. +def _compile_function_patterns(test_functions_to_remove: list[str]) -> list[re.Pattern]: + return [ + re.compile( + rf"(@pytest\.mark\.parametrize\(.*?\)\s*)?(async\s+)?def\s+{re.escape(func)}\(.*?\):.*?(?=\n(async\s+)?def\s|$)", + re.DOTALL, + ) + for func in test_functions_to_remove + ] From 62bea9e99fa2b54ec21b8037c1691cae981d70ad Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 19:21:29 -0700 Subject: [PATCH 26/34] gate async behind --async --- codeflash/cli_cmds/cli.py | 3 ++ codeflash/discovery/functions_to_optimize.py | 15 +++++- codeflash/optimization/optimizer.py | 1 + tests/test_async_function_discovery.py | 51 ++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py index 3f8f8767a..6de37397b 100644 --- a/codeflash/cli_cmds/cli.py +++ b/codeflash/cli_cmds/cli.py @@ -96,6 +96,9 @@ def parse_args() -> Namespace: ) parser.add_argument("--no-draft", default=False, action="store_true", help="Skip optimization for draft PRs") parser.add_argument("--worktree", default=False, action="store_true", help="Use worktree for optimization") + parser.add_argument( + "--async", default=False, action="store_true", help="Enable optimization of async functions. By default, async functions are excluded from optimization." + ) args, unknown_args = parser.parse_known_args() sys.argv[:] = [sys.argv[0], *unknown_args] diff --git a/codeflash/discovery/functions_to_optimize.py b/codeflash/discovery/functions_to_optimize.py index dacbb1df6..4272537f0 100644 --- a/codeflash/discovery/functions_to_optimize.py +++ b/codeflash/discovery/functions_to_optimize.py @@ -179,6 +179,7 @@ def get_functions_to_optimize( project_root: Path, module_root: Path, previous_checkpoint_functions: dict[str, dict[str, str]] | None = None, + enable_async: bool = False, ) -> tuple[dict[Path, list[FunctionToOptimize]], int, Path | None]: assert sum([bool(optimize_all), bool(replay_test), bool(file)]) <= 1, ( "Only one of optimize_all, replay_test, or file should be provided" @@ -232,7 +233,7 @@ def get_functions_to_optimize( ph("cli-optimizing-git-diff") functions = get_functions_within_git_diff(uncommitted_changes=False) filtered_modified_functions, functions_count = filter_functions( - functions, test_cfg.tests_root, ignore_paths, project_root, module_root, previous_checkpoint_functions + functions, test_cfg.tests_root, ignore_paths, project_root, module_root, previous_checkpoint_functions, enable_async=enable_async ) logger.info(f"!lsp|Found {functions_count} function{'s' if functions_count > 1 else ''} to optimize") @@ -568,6 +569,7 @@ def filter_functions( module_root: Path, previous_checkpoint_functions: dict[Path, dict[str, Any]] | None = None, disable_logs: bool = False, # noqa: FBT001, FBT002 + enable_async: bool = False, ) -> tuple[dict[Path, list[FunctionToOptimize]], int]: filtered_modified_functions: dict[str, list[FunctionToOptimize]] = {} blocklist_funcs = get_blocklisted_functions() @@ -587,6 +589,7 @@ def filter_functions( submodule_ignored_paths_count: int = 0 blocklist_funcs_removed_count: int = 0 previous_checkpoint_functions_removed_count: int = 0 + async_functions_removed_count: int = 0 tests_root_str = str(tests_root) module_root_str = str(module_root) @@ -642,6 +645,15 @@ def filter_functions( functions_tmp.append(function) _functions = functions_tmp + if not enable_async: + functions_tmp = [] + for function in _functions: + if function.is_async: + async_functions_removed_count += 1 + continue + functions_tmp.append(function) + _functions = functions_tmp + filtered_modified_functions[file_path] = _functions functions_count += len(_functions) @@ -655,6 +667,7 @@ def filter_functions( "Files from ignored submodules": (submodule_ignored_paths_count, "bright_black"), "Blocklisted functions removed": (blocklist_funcs_removed_count, "bright_red"), "Functions skipped from checkpoint": (previous_checkpoint_functions_removed_count, "green"), + "Async functions removed": (async_functions_removed_count, "bright_magenta"), } tree = Tree(Text("Ignored functions and files", style="bold")) for label, (count, color) in log_info.items(): diff --git a/codeflash/optimization/optimizer.py b/codeflash/optimization/optimizer.py index 60a32c368..50bc8341f 100644 --- a/codeflash/optimization/optimizer.py +++ b/codeflash/optimization/optimizer.py @@ -134,6 +134,7 @@ def get_optimizable_functions(self) -> tuple[dict[Path, list[FunctionToOptimize] project_root=self.args.project_root, module_root=self.args.module_root, previous_checkpoint_functions=self.args.previous_checkpoint_functions, + enable_async=getattr(self.args, "async", False), ) def create_function_optimizer( diff --git a/tests/test_async_function_discovery.py b/tests/test_async_function_discovery.py index 259d9ee24..fa648df36 100644 --- a/tests/test_async_function_discovery.py +++ b/tests/test_async_function_discovery.py @@ -236,6 +236,7 @@ def sync_method(self): ignore_paths=[], project_root=file_path.parent, module_root=file_path.parent, + enable_async=True, ) assert functions_count == 4 @@ -249,6 +250,56 @@ def sync_method(self): assert "async_func_two" not in function_names +def test_no_async_functions_finding(temp_dir): + mixed_code = """ +async def async_func_one(): + return await operation_one() + +def sync_func_one(): + return operation_one() + +async def async_func_two(): + print("no return") + +class MixedClass: + async def async_method(self): + return await self.operation() + + def sync_method(self): + return self.operation() +""" + + file_path = temp_dir / "test_file.py" + file_path.write_text(mixed_code) + + test_config = TestConfig( + tests_root="tests", + project_root_path=".", + test_framework="pytest", + tests_project_rootdir=Path() + ) + + functions, functions_count, _ = get_functions_to_optimize( + optimize_all=None, + replay_test=None, + file=file_path, + only_get_this_function=None, + test_cfg=test_config, + ignore_paths=[], + project_root=file_path.parent, + module_root=file_path.parent, + enable_async=False, + ) + + assert functions_count == 2 + + function_names = [fn.function_name for fn in functions[file_path]] + assert "sync_func_one" in function_names + assert "sync_method" in function_names + assert "async_func_one" not in function_names + assert "async_method" not in function_names + + def test_async_function_parents(temp_dir): complex_structure = """ class OuterClass: From 278822bbf645f4d2ab6378138a30975f6505d6cd Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 19:25:54 -0700 Subject: [PATCH 27/34] pass --async to E2E --- tests/scripts/end_to_end_test_async.py | 1 + tests/scripts/end_to_end_test_utilities.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tests/scripts/end_to_end_test_async.py b/tests/scripts/end_to_end_test_async.py index 5aed8f8ca..2de98c6f1 100644 --- a/tests/scripts/end_to_end_test_async.py +++ b/tests/scripts/end_to_end_test_async.py @@ -9,6 +9,7 @@ def run_test(expected_improvement_pct: int) -> bool: file_path="main.py", expected_unit_tests=0, min_improvement_x=0.1, + enable_async=True, coverage_expectations=[ CoverageExpectation( function_name="retry_with_backoff", diff --git a/tests/scripts/end_to_end_test_utilities.py b/tests/scripts/end_to_end_test_utilities.py index 6dc1aba48..90784cef9 100644 --- a/tests/scripts/end_to_end_test_utilities.py +++ b/tests/scripts/end_to_end_test_utilities.py @@ -27,6 +27,7 @@ class TestConfig: trace_mode: bool = False coverage_expectations: list[CoverageExpectation] = field(default_factory=list) benchmarks_root: Optional[pathlib.Path] = None + enable_async: bool = False def clear_directory(directory_path: str | pathlib.Path) -> None: @@ -132,6 +133,8 @@ def build_command( ) if benchmarks_root: base_command.extend(["--benchmark", "--benchmarks-root", str(benchmarks_root)]) + if config.enable_async: + base_command.append("--async") return base_command From 16ce6ffecb257235fbaa40d7fc611fac55bb99a3 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 19:27:37 -0700 Subject: [PATCH 28/34] formatter --- codeflash/cli_cmds/cli.py | 5 ++++- codeflash/discovery/functions_to_optimize.py | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py index 6de37397b..b3449fd2f 100644 --- a/codeflash/cli_cmds/cli.py +++ b/codeflash/cli_cmds/cli.py @@ -97,7 +97,10 @@ def parse_args() -> Namespace: parser.add_argument("--no-draft", default=False, action="store_true", help="Skip optimization for draft PRs") parser.add_argument("--worktree", default=False, action="store_true", help="Use worktree for optimization") parser.add_argument( - "--async", default=False, action="store_true", help="Enable optimization of async functions. By default, async functions are excluded from optimization." + "--async", + default=False, + action="store_true", + help="Enable optimization of async functions. By default, async functions are excluded from optimization.", ) args, unknown_args = parser.parse_known_args() diff --git a/codeflash/discovery/functions_to_optimize.py b/codeflash/discovery/functions_to_optimize.py index 4272537f0..48a453479 100644 --- a/codeflash/discovery/functions_to_optimize.py +++ b/codeflash/discovery/functions_to_optimize.py @@ -179,6 +179,7 @@ def get_functions_to_optimize( project_root: Path, module_root: Path, previous_checkpoint_functions: dict[str, dict[str, str]] | None = None, + *, enable_async: bool = False, ) -> tuple[dict[Path, list[FunctionToOptimize]], int, Path | None]: assert sum([bool(optimize_all), bool(replay_test), bool(file)]) <= 1, ( @@ -233,7 +234,13 @@ def get_functions_to_optimize( ph("cli-optimizing-git-diff") functions = get_functions_within_git_diff(uncommitted_changes=False) filtered_modified_functions, functions_count = filter_functions( - functions, test_cfg.tests_root, ignore_paths, project_root, module_root, previous_checkpoint_functions, enable_async=enable_async + functions, + test_cfg.tests_root, + ignore_paths, + project_root, + module_root, + previous_checkpoint_functions, + enable_async=enable_async, ) logger.info(f"!lsp|Found {functions_count} function{'s' if functions_count > 1 else ''} to optimize") @@ -568,7 +575,8 @@ def filter_functions( project_root: Path, module_root: Path, previous_checkpoint_functions: dict[Path, dict[str, Any]] | None = None, - disable_logs: bool = False, # noqa: FBT001, FBT002 + *, + disable_logs: bool = False, enable_async: bool = False, ) -> tuple[dict[Path, list[FunctionToOptimize]], int]: filtered_modified_functions: dict[str, list[FunctionToOptimize]] = {} From 7a6a432578fdc685481e140d842e84e39a005de0 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Fri, 26 Sep 2025 19:32:52 -0700 Subject: [PATCH 29/34] mypy fix --- codeflash/code_utils/edit_generated_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codeflash/code_utils/edit_generated_tests.py b/codeflash/code_utils/edit_generated_tests.py index 16fabefe3..abbcb68c1 100644 --- a/codeflash/code_utils/edit_generated_tests.py +++ b/codeflash/code_utils/edit_generated_tests.py @@ -237,7 +237,7 @@ def remove_functions_from_generated_tests( # Pre-compile all function removal regexes upfront for efficiency. -def _compile_function_patterns(test_functions_to_remove: list[str]) -> list[re.Pattern]: +def _compile_function_patterns(test_functions_to_remove: list[str]) -> list[re.Pattern[str]]: return [ re.compile( rf"(@pytest\.mark\.parametrize\(.*?\)\s*)?(async\s+)?def\s+{re.escape(func)}\(.*?\):.*?(?=\n(async\s+)?def\s|$)", From 585249f31606cb005af2fa52329c2ce1216d3bd4 Mon Sep 17 00:00:00 2001 From: "codeflash-ai[bot]" <148906541+codeflash-ai[bot]@users.noreply.github.com> Date: Sat, 27 Sep 2025 02:50:07 +0000 Subject: [PATCH 30/34] Optimize AsyncCallInstrumenter.visit_AsyncFunctionDef The optimized code achieves a **123% speedup** by replacing expensive AST traversal operations with more efficient alternatives: **Key Optimizations:** 1. **Decorator Search Optimization**: Replaced the `any()` generator expression with a simple loop that breaks early when finding `timeout_decorator.timeout`. This avoids unnecessary attribute lookups and iterations through the decorator list, especially beneficial when the decorator is found early or when there are many decorators. 2. **AST Traversal Replacement**: The most significant optimization replaces `ast.walk(stmt)` with a manual stack-based depth-first search in `_optimized_instrument_statement()`. The original `ast.walk()` creates a list of every node in the AST subtree, which is memory-intensive and includes many irrelevant nodes. The optimized version: - Uses a stack to traverse nodes manually - Only explores child nodes via `_fields` attribute access - Immediately returns when finding an `ast.Await` node that matches criteria - Avoids creating intermediate collections **Performance Impact by Test Case:** - **Large-scale tests** see the biggest improvements (125-129% faster) because they have many statements to traverse - **Nested structures** benefit significantly (57-93% faster) as the optimization avoids deep, unnecessary traversals - **Simple test cases** still see 29-48% improvements from the decorator optimization - **Functions with many await calls** show excellent scaling (123-127% faster) due to reduced per-statement traversal costs The line profiler shows the critical bottleneck was in `_instrument_statement()` (96.4% of time originally), which is now reduced to 93.3% but with much lower absolute time, demonstrating the effectiveness of the AST traversal optimization. --- .../code_utils/instrument_existing_tests.py | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/codeflash/code_utils/instrument_existing_tests.py b/codeflash/code_utils/instrument_existing_tests.py index db08f8afc..7e7940ddd 100644 --- a/codeflash/code_utils/instrument_existing_tests.py +++ b/codeflash/code_utils/instrument_existing_tests.py @@ -351,16 +351,24 @@ def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: def _process_test_function( self, node: ast.AsyncFunctionDef | ast.FunctionDef ) -> ast.AsyncFunctionDef | ast.FunctionDef: - if self.test_framework == "unittest" and not any( - isinstance(d, ast.Call) and isinstance(d.func, ast.Name) and d.func.id == "timeout_decorator.timeout" - for d in node.decorator_list - ): - timeout_decorator = ast.Call( - func=ast.Name(id="timeout_decorator.timeout", ctx=ast.Load()), - args=[ast.Constant(value=15)], - keywords=[], - ) - node.decorator_list.append(timeout_decorator) + # Optimize the search for decorator presence + if self.test_framework == "unittest": + found_timeout = False + for d in node.decorator_list: + # Avoid isinstance(d.func, ast.Name) if d is not ast.Call + if isinstance(d, ast.Call): + f = d.func + # Avoid attribute lookup if f is not ast.Name + if isinstance(f, ast.Name) and f.id == "timeout_decorator.timeout": + found_timeout = True + break + if not found_timeout: + timeout_decorator = ast.Call( + func=ast.Name(id="timeout_decorator.timeout", ctx=ast.Load()), + args=[ast.Constant(value=15)], + keywords=[], + ) + node.decorator_list.append(timeout_decorator) # Initialize counter for this test function if node.name not in self.async_call_counter: @@ -368,8 +376,9 @@ def _process_test_function( new_body = [] + # Optimize ast.walk calls inside _instrument_statement, by scanning only relevant nodes for _i, stmt in enumerate(node.body): - transformed_stmt, added_env_assignment = self._instrument_statement(stmt, node.name) + transformed_stmt, added_env_assignment = self._optimized_instrument_statement(stmt) if added_env_assignment: current_call_index = self.async_call_counter[node.name] @@ -423,6 +432,26 @@ def _call_in_positions(self, call_node: ast.Call) -> bool: return node_in_call_position(call_node, self.call_positions) + # Optimized version: only walk child nodes for Await + def _optimized_instrument_statement(self, stmt: ast.stmt) -> tuple[ast.stmt, bool]: + # Stack-based DFS, manual for relevant Await nodes + stack = [stmt] + while stack: + node = stack.pop() + # Favor direct ast.Await detection + if isinstance(node, ast.Await): + val = node.value + if isinstance(val, ast.Call) and self._is_target_call(val) and self._call_in_positions(val): + return stmt, True + # Use _fields instead of ast.walk for less allocations + for fname in getattr(node, "_fields", ()): + child = getattr(node, fname, None) + if isinstance(child, list): + stack.extend(child) + elif isinstance(child, ast.AST): + stack.append(child) + return stmt, False + class FunctionImportedAsVisitor(ast.NodeVisitor): """Checks if a function has been imported as an alias. We only care about the alias then. From 2c8d2ba2923ac5b71942d2a097a816ac8e9f59c1 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Sun, 28 Sep 2025 04:13:14 -0700 Subject: [PATCH 31/34] make asyncio optional --- pyproject.toml | 10 +- uv.lock | 1037 ++++++++++++++++++++++++++---------------------- 2 files changed, 563 insertions(+), 484 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 20a0a1fe8..226c9f3ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,6 @@ dependencies = [ "pygls>=1.3.1", "codeflash-benchmark", "filelock", - "pytest-asyncio>=1.2.0", ] [project.urls] @@ -53,8 +52,14 @@ Homepage = "https://codeflash.ai" [project.scripts] codeflash = "codeflash.main:main" +[project.optional-dependencies] +asyncio = [ + "pytest-asyncio>=1.2.0", +] + [dependency-groups] dev = [ + {include-group = "asyncio"}, "ipython>=8.12.0", "mypy>=1.13", "ruff>=0.7.0", @@ -77,6 +82,9 @@ dev = [ "uv>=0.6.2", "pre-commit>=4.2.0,<5", ] +asyncio = [ + "pytest-asyncio>=1.2.0", +] [tool.hatch.build.targets.sdist] include = ["codeflash"] diff --git a/uv.lock b/uv.lock index 8967d1b0e..c08cf5cb0 100644 --- a/uv.lock +++ b/uv.lock @@ -71,15 +71,15 @@ wheels = [ [[package]] name = "blessed" -version = "1.21.0" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinxed", marker = "sys_platform == 'win32'" }, { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/5e/3cada2f7514ee2a76bb8168c71f9b65d056840ebb711962e1ec08eeaa7b0/blessed-1.21.0.tar.gz", hash = "sha256:ece8bbc4758ab9176452f4e3a719d70088eb5739798cd5582c9e05f2a28337ec", size = 6660011, upload-time = "2025-04-26T21:56:58.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/51/a72df7730aa34a94bc43cebecb7b63ffa42f019868637dbeb45e0620d26e/blessed-1.22.0.tar.gz", hash = "sha256:1818efb7c10015478286f21a412fcdd31a3d8b94a18f6d926e733827da7a844b", size = 6660050, upload-time = "2025-09-15T19:15:26.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/8e/0a37e44878fd76fac9eff5355a1bf760701f53cb5c38cdcd59a8fd9ab2a2/blessed-1.21.0-py2.py3-none-any.whl", hash = "sha256:f831e847396f5a2eac6c106f4dfadedf46c4f804733574b15fe86d2ed45a9588", size = 84727, upload-time = "2025-04-26T16:58:29.919Z" }, + { url = "https://files.pythonhosted.org/packages/11/b7/a19b55c4cd0b5ca5009ca11d3634994758a1a446976b8e7afa25e719613c/blessed-1.22.0-py2.py3-none-any.whl", hash = "sha256:a1fed52d708a1aa26dfb8d3eaecf6f4714bff590e728baeefcb44f2c16c8de82", size = 85078, upload-time = "2025-09-15T19:15:24.787Z" }, ] [[package]] @@ -207,7 +207,7 @@ wheels = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", @@ -217,9 +217,9 @@ resolution-markers = [ dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] [[package]] @@ -227,7 +227,7 @@ name = "codeflash" source = { editable = "." } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "codeflash-benchmark" }, { name = "coverage" }, { name = "crosshair-tool" }, @@ -249,7 +249,6 @@ dependencies = [ { name = "pydantic" }, { name = "pygls" }, { name = "pytest" }, - { name = "pytest-asyncio" }, { name = "pytest-timeout" }, { name = "rich" }, { name = "sentry-sdk" }, @@ -259,7 +258,15 @@ dependencies = [ { name = "unittest-xml-reporting" }, ] +[package.optional-dependencies] +asyncio = [ + { name = "pytest-asyncio" }, +] + [package.dev-dependencies] +asyncio = [ + { name = "pytest-asyncio" }, +] dev = [ { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, @@ -269,6 +276,7 @@ dev = [ { name = "pandas-stubs", version = "2.2.2.240807", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pandas-stubs", version = "2.2.2.240909", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pre-commit" }, + { name = "pytest-asyncio" }, { name = "ruff" }, { name = "types-cffi" }, { name = "types-colorama" }, @@ -310,7 +318,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=1.10.1" }, { name = "pygls", specifier = ">=1.3.1" }, { name = "pytest", specifier = ">=7.0.0,!=8.3.4" }, - { name = "pytest-asyncio", specifier = ">=1.2.0" }, + { name = "pytest-asyncio", marker = "extra == 'asyncio'", specifier = ">=1.2.0" }, { name = "pytest-timeout", specifier = ">=2.1.0" }, { name = "rich", specifier = ">=13.8.1" }, { name = "sentry-sdk", specifier = ">=1.40.6,<3.0.0" }, @@ -319,14 +327,17 @@ requires-dist = [ { name = "unidiff", specifier = ">=0.7.4" }, { name = "unittest-xml-reporting", specifier = ">=3.2.0" }, ] +provides-extras = ["asyncio"] [package.metadata.requires-dev] +asyncio = [{ name = "pytest-asyncio", specifier = ">=1.2.0" }] dev = [ { name = "ipython", specifier = ">=8.12.0" }, { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "mypy", specifier = ">=1.13" }, { name = "pandas-stubs", specifier = ">=2.2.2.240807,<2.2.3.241009" }, { name = "pre-commit", specifier = ">=4.2.0,<5" }, + { name = "pytest-asyncio", specifier = ">=1.2.0" }, { name = "ruff", specifier = ">=0.7.0" }, { name = "types-cffi", specifier = ">=1.16.0.20240331" }, { name = "types-colorama", specifier = ">=0.4.15.20240311" }, @@ -367,102 +378,118 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025, upload-time = "2025-08-29T15:32:57.169Z" }, - { url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419, upload-time = "2025-08-29T15:32:59.908Z" }, - { url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180, upload-time = "2025-08-29T15:33:01.608Z" }, - { url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992, upload-time = "2025-08-29T15:33:03.239Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851, upload-time = "2025-08-29T15:33:04.603Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891, upload-time = "2025-08-29T15:33:06.176Z" }, - { url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909, upload-time = "2025-08-29T15:33:07.74Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786, upload-time = "2025-08-29T15:33:08.965Z" }, - { url = "https://files.pythonhosted.org/packages/d0/4c/37ed872374a21813e0d3215256180c9a382c3f5ced6f2e5da0102fc2fd3e/coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", size = 219521, upload-time = "2025-08-29T15:33:10.599Z" }, - { url = "https://files.pythonhosted.org/packages/8e/36/9311352fdc551dec5b973b61f4e453227ce482985a9368305880af4f85dd/coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", size = 220417, upload-time = "2025-08-29T15:33:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, - { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, - { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, - { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, - { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, - { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, - { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, - { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, - { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, - { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, - { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, - { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, - { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, - { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, - { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" }, - { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" }, - { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" }, - { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" }, - { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" }, - { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" }, - { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" }, - { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" }, - { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" }, - { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" }, - { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" }, - { url = "https://files.pythonhosted.org/packages/91/70/f73ad83b1d2fd2d5825ac58c8f551193433a7deaf9b0d00a8b69ef61cd9a/coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352", size = 217009, upload-time = "2025-08-29T15:34:57.381Z" }, - { url = "https://files.pythonhosted.org/packages/01/e8/099b55cd48922abbd4b01ddd9ffa352408614413ebfc965501e981aced6b/coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612", size = 217400, upload-time = "2025-08-29T15:34:58.985Z" }, - { url = "https://files.pythonhosted.org/packages/ee/d1/c6bac7c9e1003110a318636fef3b5c039df57ab44abcc41d43262a163c28/coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b", size = 243835, upload-time = "2025-08-29T15:35:00.541Z" }, - { url = "https://files.pythonhosted.org/packages/01/f9/82c6c061838afbd2172e773156c0aa84a901d59211b4975a4e93accf5c89/coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144", size = 245658, upload-time = "2025-08-29T15:35:02.135Z" }, - { url = "https://files.pythonhosted.org/packages/81/6a/35674445b1d38161148558a3ff51b0aa7f0b54b1def3abe3fbd34efe05bc/coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b", size = 247433, upload-time = "2025-08-29T15:35:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/98c99e7cafb288730a93535092eb433b5503d529869791681c4f2e2012a8/coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862", size = 245315, upload-time = "2025-08-29T15:35:05.629Z" }, - { url = "https://files.pythonhosted.org/packages/09/05/123e0dba812408c719c319dea05782433246f7aa7b67e60402d90e847545/coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2", size = 243385, upload-time = "2025-08-29T15:35:07.494Z" }, - { url = "https://files.pythonhosted.org/packages/67/52/d57a42502aef05c6325f28e2e81216c2d9b489040132c18725b7a04d1448/coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78", size = 244343, upload-time = "2025-08-29T15:35:09.55Z" }, - { url = "https://files.pythonhosted.org/packages/6b/22/7f6fad7dbb37cf99b542c5e157d463bd96b797078b1ec506691bc836f476/coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c", size = 219530, upload-time = "2025-08-29T15:35:11.167Z" }, - { url = "https://files.pythonhosted.org/packages/62/30/e2fda29bfe335026027e11e6a5e57a764c9df13127b5cf42af4c3e99b937/coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf", size = 220432, upload-time = "2025-08-29T15:35:12.902Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, ] [[package]] name = "crosshair-tool" -version = "0.0.95" +version = "0.0.96" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, @@ -473,43 +500,43 @@ dependencies = [ { name = "typing-inspect" }, { name = "z3-solver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/7e/4a811ebfb5d6659499d62fabb0ac3902593300c82b9e8fae1c0d33800b22/crosshair_tool-0.0.95.tar.gz", hash = "sha256:468a5fe9db949b2cc5132cce7650c6fdceb53d80bb1d0148763d6d8d4b9f632d", size = 469358, upload-time = "2025-08-01T03:39:52.589Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/f5/46815ac294c90bdbc92f628d2e45cb6db980c9ef28235be6893adf4deeb2/crosshair_tool-0.0.95-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7f7db95ba73ee8860d02a16cca284ec317a980c9dc4a1e105fba22d34820be66", size = 531683, upload-time = "2025-08-01T03:39:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/ae/b9/8c5e745c196cc1c912e91ea573305d72197e2dcc08e9e417cb1fb4962573/crosshair_tool-0.0.95-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a55ea6450e8ab10cf2e512757b0b0cb6390c526b46b8d24c3f99535a9779ef8", size = 523769, upload-time = "2025-08-01T03:39:05.372Z" }, - { url = "https://files.pythonhosted.org/packages/34/a6/203c5ad753e232f9ba28dc645bf4b6ac25f52919e8f0bdbeeb4eeaa0ed34/crosshair_tool-0.0.95-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c8be2c1da20e0357bbccee36117542b55dd4e148ece9e7a23bc7d13d5010f880", size = 524538, upload-time = "2025-08-01T03:39:06.677Z" }, - { url = "https://files.pythonhosted.org/packages/65/47/c66cbf9e4d5c11e0541c94e12a4d15eff6559258c400256a0a3239e7c3ef/crosshair_tool-0.0.95-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f81b305c3f9a25e7c51cd48e033f1f131bcc483670ff2ed5ea2fa2bf44708480", size = 548236, upload-time = "2025-08-01T03:39:07.888Z" }, - { url = "https://files.pythonhosted.org/packages/42/b2/3338ec591cca6ac07a7400a6d3ea4ae5044c588eee90d8dbd8bc1d3daa53/crosshair_tool-0.0.95-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:48b612d87bcbc78577430a1c5cd0c5c1b3a5970ac72b8d0aa2b13ad3aa154044", size = 547025, upload-time = "2025-08-01T03:39:08.799Z" }, - { url = "https://files.pythonhosted.org/packages/bb/42/bd8472ad9c6a16439788e43990d0ad20012830b9b04a1e31931b8e1b6df9/crosshair_tool-0.0.95-cp310-cp310-win32.whl", hash = "sha256:463b0aab782b26bda2e846e668a8c43e3a13e9747fd6679b8fce37992f6d6096", size = 526719, upload-time = "2025-08-01T03:39:10.204Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0a/349c4fdcf108ae20a1a7230a68f2e25d6f906996a4becdc89daa3ec6a1b0/crosshair_tool-0.0.95-cp310-cp310-win_amd64.whl", hash = "sha256:27001994bc4992ce805118837fdcfce39fb172d8f566b5d6a2abd7e2787ae158", size = 527704, upload-time = "2025-08-01T03:39:11.464Z" }, - { url = "https://files.pythonhosted.org/packages/14/91/68ad67d8c4e0747376c605f570ef6ec821b36704e90cd76b3fa1bb18c04c/crosshair_tool-0.0.95-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:301f8249dbf98efa07673abd2447f12eea928dfa72024774aa6d0dacdd49a229", size = 531771, upload-time = "2025-08-01T03:39:12.476Z" }, - { url = "https://files.pythonhosted.org/packages/2c/33/7c72bbc0d169556ac04b9fa03c7bfced72bfb0aea58d9645ea3317f13b10/crosshair_tool-0.0.95-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e41dfb798ab62a1b0c3b1f28f27d156a194d0fc9daadf03fdb47ff8770ab742", size = 523818, upload-time = "2025-08-01T03:39:13.451Z" }, - { url = "https://files.pythonhosted.org/packages/a9/75/29d93db9c5cda1c929cee57045ee8116c7ac3f3ee3d78112450d9fbd1438/crosshair_tool-0.0.95-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28ac57f90063dc3747067bf658031ff9f12d18bf3152652eb5d812909b67fcc9", size = 524585, upload-time = "2025-08-01T03:39:14.739Z" }, - { url = "https://files.pythonhosted.org/packages/aa/9f/cc8059dd7971a5a0d226dc5352908ab1852c5901da1885c023c76d1e2bb7/crosshair_tool-0.0.95-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4be6f7dd88a63e37591385602beb496f9d04e77b1884c42c53f5b4decbc1059d", size = 548477, upload-time = "2025-08-01T03:39:15.704Z" }, - { url = "https://files.pythonhosted.org/packages/bf/72/0103e6c3af2d0bba3199b4aef77fe9f4635aa5f4e5bd3c0e3a3790e80cfa/crosshair_tool-0.0.95-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:76eb8727055dc6a420ca58da7dc9a1c8d51088312888baa584eb65289347c9fb", size = 547403, upload-time = "2025-08-01T03:39:16.967Z" }, - { url = "https://files.pythonhosted.org/packages/33/13/d684dfd428cceea03c911d77576633e03cf0fc667517d22ebc471da2e8dc/crosshair_tool-0.0.95-cp311-cp311-win32.whl", hash = "sha256:b82e5367013cdc5f832b5f80de0b05d3a3914ddf2f40a9c323ecae6b02b21de5", size = 526747, upload-time = "2025-08-01T03:39:18.708Z" }, - { url = "https://files.pythonhosted.org/packages/ef/7e/ad832ee3f3aa84866e03fcf4e831485ba7083ae6aaa5c00f5f5dd40dcce2/crosshair_tool-0.0.95-cp311-cp311-win_amd64.whl", hash = "sha256:4b54eb987ffdb76e2221da4c488acbc88f5c5cf6ddf17faf937527e7e0d46100", size = 527751, upload-time = "2025-08-01T03:39:19.613Z" }, - { url = "https://files.pythonhosted.org/packages/ab/29/45bfce63004a20977194d2609a75057b13f326f0f356c6d6b05db7490932/crosshair_tool-0.0.95-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa20d8d624e226167ce2c27f5fa9cf9a2646edfffa7b05f49efe2a7902ec5fe4", size = 535662, upload-time = "2025-08-01T03:39:20.924Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e1/60f16026d6e0a7f03bd9f2305ad0e945e2c00990ff4aede4cec40da94ae1/crosshair_tool-0.0.95-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:82f06c1a7fbfa26b7b58f614888e46cf393626e638bc2fcc043b2cc944533c1b", size = 526243, upload-time = "2025-08-01T03:39:21.871Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b0/34e8afff2c575704ff79c8b4f24551be24bdedfaa4d3818698daf168d336/crosshair_tool-0.0.95-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecdeedaf73fd270c07426213718535e0aa64fecb99ff3e95627a97ecf08ad8e6", size = 526871, upload-time = "2025-08-01T03:39:22.993Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c1/598526215ca298d12052d35c5323bc1344e94e85da25e6391c9824f33c77/crosshair_tool-0.0.95-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e57cce9c0762eccdc2e1bb94619c0ee3491fd006413c33651810cf6e05a1b6b", size = 557463, upload-time = "2025-08-01T03:39:23.931Z" }, - { url = "https://files.pythonhosted.org/packages/12/a6/34931b6319fbdc8ecd5234f88a225691fe14e5a1124c3ce10db91d14cc70/crosshair_tool-0.0.95-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8f51bb082ff4a65d5cbddd01edd7619f11b5e5647b290507fe6e8f85553c8ce2", size = 556352, upload-time = "2025-08-01T03:39:25.304Z" }, - { url = "https://files.pythonhosted.org/packages/00/54/87e29720a3d0808bd4edefdfa79c3449811231529ab3ba5c7421ecb4a175/crosshair_tool-0.0.95-cp312-cp312-win32.whl", hash = "sha256:397325604942f388625f9e003031e8658445b209de803ab4931e44ffac022d37", size = 528424, upload-time = "2025-08-01T03:39:26.516Z" }, - { url = "https://files.pythonhosted.org/packages/b4/52/b0a1894558bdd93d7a9fe00ae7fe9098c19636e9501d26ba4e0753cf1bdd/crosshair_tool-0.0.95-cp312-cp312-win_amd64.whl", hash = "sha256:fabb0a7803783b4b6fd952f6ecf7f5149ba7df0098e4ae0ecf9156f6ec8fe91d", size = 529564, upload-time = "2025-08-01T03:39:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/66/2e/a3769cb1ca5be6d11dcf3987d9fa13d4016eed4f76b80219852d1b73117c/crosshair_tool-0.0.95-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0710f91067b9aa7ca477deed230999de25358078528feafc54ae883e1470675c", size = 544314, upload-time = "2025-08-01T03:39:28.743Z" }, - { url = "https://files.pythonhosted.org/packages/da/8f/335985bf4293a57c4c7a70d963b4dd3ce6fdb1f2810a18b20a49c692ac09/crosshair_tool-0.0.95-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47054217931c255259491181f2ae1e618432615bb2fe9eda71b2282f6f0955aa", size = 530018, upload-time = "2025-08-01T03:39:29.708Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c9/df7090d3c6c3e84860e80d457e37991d20614e9ac515ca0d3a465a46bc51/crosshair_tool-0.0.95-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a9ebd147a99de46ca0736735f8c73e755152e58c293de620fbb53889ba32c9f3", size = 530699, upload-time = "2025-08-01T03:39:30.628Z" }, - { url = "https://files.pythonhosted.org/packages/f3/0b/1eaa8a3c253eaf6ae191667675b99bc1c29105c568d0a2f4821e06fa06bf/crosshair_tool-0.0.95-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f208253b87b7174bc13f50c1f844a3db6a0653142b24b3c2635e848ac72cf27", size = 563676, upload-time = "2025-08-01T03:39:31.589Z" }, - { url = "https://files.pythonhosted.org/packages/a8/42/ddb80a0b9187eab1d6f1400480202343552a1bdb202f5d2c43ce3ba5b8cf/crosshair_tool-0.0.95-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55c1c6ae6df75fefa229fa96333652f64a72230b09f2224d2a4f8cd7282fc1ce", size = 563040, upload-time = "2025-08-01T03:39:32.922Z" }, - { url = "https://files.pythonhosted.org/packages/40/aa/3a5c0987f8e2e7069c8096515203fa20ea15d2be74363f2d6187242068da/crosshair_tool-0.0.95-cp313-cp313-win32.whl", hash = "sha256:db890506c5c5a0557268cc603a8c472a49300ecb579691d15eb7e27539d8f055", size = 528450, upload-time = "2025-08-01T03:39:33.906Z" }, - { url = "https://files.pythonhosted.org/packages/0b/89/86da28b169be0a08d87381f97ca94ca529c97fcf338aa4bf43dd3c404621/crosshair_tool-0.0.95-cp313-cp313-win_amd64.whl", hash = "sha256:248dd1d266b4e306d5628d31f06108b07a3f9588f3678be0c23ace352b57051a", size = 529599, upload-time = "2025-08-01T03:39:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/10/65/4c7da3bf7454ecc3e90251d427cb407ddcfe0f66a70acb0e4e2a046ee551/crosshair_tool-0.0.95-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c72ea2600a48ca9c98bfddce49541e6179c0f0a05ed56c58e0d09701155f434e", size = 531592, upload-time = "2025-08-01T03:39:44.464Z" }, - { url = "https://files.pythonhosted.org/packages/6f/92/49aa5cf86b0b042f37809cc411a76b8cc27e6203914655222de815f1a417/crosshair_tool-0.0.95-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:706a3f6862a744ea43afd0300bcec3230b89d634fd15c7faa3e19decae5c082a", size = 523714, upload-time = "2025-08-01T03:39:45.518Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0d/27674671519722b78b4fcc958c29cf57699b81d7d9ebb6796cb47cdecd50/crosshair_tool-0.0.95-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6ab1160e040f56c5f13021741baa9708e41c96d8ef07d8fc04297900240abfb", size = 524513, upload-time = "2025-08-01T03:39:46.929Z" }, - { url = "https://files.pythonhosted.org/packages/14/22/b876dd2347da3634512b3797066362cae4bec0a5c05688a668383a06c965/crosshair_tool-0.0.95-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78c957bdf55eaf872c22458b9b16dc747359f892e5c7b96d99cba9ef033b41a8", size = 547484, upload-time = "2025-08-01T03:39:48.227Z" }, - { url = "https://files.pythonhosted.org/packages/ef/48/fc92c91f1739b6dc4ad1fe62e78449b50c8fde92cb44a82bd154261e1ded/crosshair_tool-0.0.95-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6cc05ad65be99600bcba39e55ec11f790f408c5976f7d15740968e2cad482f37", size = 546336, upload-time = "2025-08-01T03:39:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/6c/59/8895909f77d99caf2c4ebc8d4bcd7444cff913f1cdd26cc50e2dce21686c/crosshair_tool-0.0.95-cp39-cp39-win32.whl", hash = "sha256:8b11cfd3446938701df3288baaac846208b7ecc231e5c7f8546fd1354c3fbede", size = 526719, upload-time = "2025-08-01T03:39:50.609Z" }, - { url = "https://files.pythonhosted.org/packages/ec/af/8a5c0972d8fce3479ea69775b0195cfc88db5e979a1d4556846e97d68ba2/crosshair_tool-0.0.95-cp39-cp39-win_amd64.whl", hash = "sha256:3626a94d5b4daa2a05485e576bf7771f7b95de6cac6ac729aebe3955e68a3557", size = 527716, upload-time = "2025-08-01T03:39:51.625Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/31/89/2da3f9f40d1d75f277119500d25b6482247c6358c44ab6bc854f2c65c78a/crosshair_tool-0.0.96.tar.gz", hash = "sha256:c4ebbea95324d422a6632232d3f3736799add6e258bd599868e84fbeb08655f4", size = 472106, upload-time = "2025-09-23T18:22:46.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/e5/1cde023c12f3e11dc902f375b3a8335118dd6c9af48635738e5e7579b450/crosshair_tool-0.0.96-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec9ca49170b12e904cb90bfae997df8729b3bbfb6270511d4692167e42d7af62", size = 534327, upload-time = "2025-09-23T18:21:52.01Z" }, + { url = "https://files.pythonhosted.org/packages/b7/78/36f8cb6bffbbd13f667a761fa5d55c5f4257725d8e7c0bcac839de1fa2b3/crosshair_tool-0.0.96-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a3278df141f6919fe1016d254f75a6c578a62201370cda12be92796218d70fb", size = 526415, upload-time = "2025-09-23T18:21:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f1/e687a4313300bbcc7870fa1f4c652350646a4b58281128976c4f91d0563a/crosshair_tool-0.0.96-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cd10e74efea5d0fafbae5b22e87246ff90888eba3038d03b1939a042c030d114", size = 527191, upload-time = "2025-09-23T18:21:55.512Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ee/b68da67e4d189abe4e633ba06ce860e2c153986c1f96b0da469a441ecf23/crosshair_tool-0.0.96-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2335218d3ee6f87f7c76f312982ecdf63e92c9958238475c238af18381f31d", size = 550887, upload-time = "2025-09-23T18:21:56.582Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3e/fa8b828e650c0f6ec22ba284a52f94d953fae720222aaea42ac4e754de29/crosshair_tool-0.0.96-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cd7ec304dabddcb8d6a3ceb3a1a0f8926a8a1732d9b73a9275d4c40af347deed", size = 549675, upload-time = "2025-09-23T18:21:57.577Z" }, + { url = "https://files.pythonhosted.org/packages/21/e9/23a10e899daed8def2506a7280aaeecad748da21d8c2d602ffb7c72c5138/crosshair_tool-0.0.96-cp310-cp310-win32.whl", hash = "sha256:00b37d3dedc6be4b7cd98670275e7ae2baf0aedd2c31de39f56bb4e39ad5e734", size = 529398, upload-time = "2025-09-23T18:21:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/31/b8/c9f023af483be680495330d57e96cebc4ef4664fa320f1bc250060681e05/crosshair_tool-0.0.96-cp310-cp310-win_amd64.whl", hash = "sha256:44c520c6908954a5d70bf3f0f305c413ecf7bfa27f680750c4c5ae8f9a30ca32", size = 530380, upload-time = "2025-09-23T18:22:00.136Z" }, + { url = "https://files.pythonhosted.org/packages/28/b5/c489382d9faf4dfa6852f2909b0ea974d3916ecf06141f9a9024b0e58a98/crosshair_tool-0.0.96-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ebe9bcfd0b2c19e7f67061204e0cde3c6a201df066da75e88a70b82561bf42ee", size = 534420, upload-time = "2025-09-23T18:22:01.298Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c7/f125a2f909396043a90defd5c03a8ecfbb12c985b9f7a345b3669a2cea61/crosshair_tool-0.0.96-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8bbcb7983cf7f08f66fc3a1389739d47228df4d08dbc0d67b56a90abf091f95", size = 526466, upload-time = "2025-09-23T18:22:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/7d207f7c6c03ec64f5a77ca48efe2feb4a3e87ccdc91fb802b3477292247/crosshair_tool-0.0.96-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e82d80259a1601ea5d93ae0c530d0052c47691f66a574c8f6d664be47d48f950", size = 527235, upload-time = "2025-09-23T18:22:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/df/b1/36e6df974452640a92a8961f5a6ad4705384540a562c2532512856856716/crosshair_tool-0.0.96-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c3caf1ff0d56a77819ed9ed3440f84ac7746958d4423a6cb8323a413c603f05", size = 551127, upload-time = "2025-09-23T18:22:05.638Z" }, + { url = "https://files.pythonhosted.org/packages/50/92/8d9e484996d0937673252cbab384871cb3328b8c6176ab243b414f30f544/crosshair_tool-0.0.96-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47cbfa2b809e6dd77a41bdcaf0c4de298880619dd761adac547e2cb38c344b13", size = 550052, upload-time = "2025-09-23T18:22:06.873Z" }, + { url = "https://files.pythonhosted.org/packages/2c/56/e0f526402e14171041b5d8bdd0a183c1200ec8b9af88cb149369fb238881/crosshair_tool-0.0.96-cp311-cp311-win32.whl", hash = "sha256:2891832780f7e22006ed3a7256b16c0992aeb269c81326c41c20a86779ebde5d", size = 529430, upload-time = "2025-09-23T18:22:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/9a1dc9eb5548796383967fed7c69a5983f34ca5e8cc049300f0f0fbb6bf5/crosshair_tool-0.0.96-cp311-cp311-win_amd64.whl", hash = "sha256:fb5830596981dd2b04b7b7b34250ec90a19cbe4c944ae26691af53d12c8850b4", size = 530429, upload-time = "2025-09-23T18:22:09.244Z" }, + { url = "https://files.pythonhosted.org/packages/3c/ce/56a4bb0ba82d0f7aa671417d912ee866bf61d1d9aba9f3baf0f0829728ac/crosshair_tool-0.0.96-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3c45fac42cfcbb4db861c1120c93da1badafed820126795339750d8f784abbf1", size = 538310, upload-time = "2025-09-23T18:22:10.259Z" }, + { url = "https://files.pythonhosted.org/packages/8c/61/05217cc2e0907abcabf286a570416481385a9ecf79cfb6bceedf1a85a746/crosshair_tool-0.0.96-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1b673785e2fe3cd5b2fb87f1871e622f9def31e594deea0ff2c723b60b65469", size = 528891, upload-time = "2025-09-23T18:22:11.289Z" }, + { url = "https://files.pythonhosted.org/packages/84/91/e558192ca8fdc1afc0e5359e2df88f9215c142abc092e5a6b4d6b6afc977/crosshair_tool-0.0.96-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b34d9a4c6aad7c224db86b1b5a15d32dc0592009085178965ef6304f7f91c337", size = 529519, upload-time = "2025-09-23T18:22:12.444Z" }, + { url = "https://files.pythonhosted.org/packages/ed/eb/e0e11d4978f2c431e5b49459701027ce4c4cb375bb5040381e0d130fc95b/crosshair_tool-0.0.96-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd43943755e0f82e30b8bcac536dc4cb85f452991c4b0104f645f73a40ed3f7", size = 560113, upload-time = "2025-09-23T18:22:13.893Z" }, + { url = "https://files.pythonhosted.org/packages/42/cd/452600efa2002479419252753c46b49d1f18c69bf0ecf5c3927065209727/crosshair_tool-0.0.96-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4fa527441e619b2b4cadbf4cf4c14e9db7ffbff8074a8b432f2cfb8461db10ed", size = 559004, upload-time = "2025-09-23T18:22:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4c/61c09a5b9b61bacd3d440cf10968142ecab92f887ae145b487431e8e310e/crosshair_tool-0.0.96-cp312-cp312-win32.whl", hash = "sha256:90460bf20e25444827f816f455fdd454175b3c53cd11a1bfed53ff38673b1a60", size = 531102, upload-time = "2025-09-23T18:22:15.976Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f9/61dbe4830804010275b46fb0caf0c62337555ed5fe4de6831f502d5a3146/crosshair_tool-0.0.96-cp312-cp312-win_amd64.whl", hash = "sha256:baf4c92a874bfa7737c1c5a2f9a5784c71ef0e41dc7dcb23f7c49f74384409f7", size = 532238, upload-time = "2025-09-23T18:22:17.073Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cc/67cbf11a2ddf134aa2f1a95d698d9f1f8ed0a39358b7e6c495b529b18837/crosshair_tool-0.0.96-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f5a3bce6e7b7cf02a87ddf6c503c53d72a3fe53f66758672e97afacadc3b873f", size = 546960, upload-time = "2025-09-23T18:22:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/88/b4/183bb1a70b92c6289dcef4d47e06bbf9bbf00efa43d181a3cc2235d0f8fe/crosshair_tool-0.0.96-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fb08921afa832447b80cb39e0276bb7bcd5bab4099c4ea7ebfd0f98d206c0125", size = 532666, upload-time = "2025-09-23T18:22:19.749Z" }, + { url = "https://files.pythonhosted.org/packages/68/60/23c889c34b1e03f572707b3edbd44682c35e04186397efc06b3da8cf1b30/crosshair_tool-0.0.96-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f402d9241ec8159917f790a6148607a68cbec5badc1d084b4aa41ecbd402dd2f", size = 533347, upload-time = "2025-09-23T18:22:21.156Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ab/8957b1fde965c9d813ae78157d6b1a937574c97e98938b823e715a49e4cc/crosshair_tool-0.0.96-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bdc6e01f04cede7e803516c7388f9fcf4092a1361fb6336c4e92ef989a3e74c", size = 566325, upload-time = "2025-09-23T18:22:22.614Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/a8e83d6bd46641ec29e07e259dbffed696d5f7a88106bd3678f5c3ac7d1f/crosshair_tool-0.0.96-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ae7734c6334a2ce1588b63266c66511b6855e6ae877b0f52afa396db21145ac", size = 565688, upload-time = "2025-09-23T18:22:23.684Z" }, + { url = "https://files.pythonhosted.org/packages/13/38/5d3efc2432bfe7b2ef0a83a558ab6927999a287f24804bea843b57a67f85/crosshair_tool-0.0.96-cp313-cp313-win32.whl", hash = "sha256:9831b4d496da5566d84564e586c3a552567bd01b0d3059ce58da06aca935269c", size = 531129, upload-time = "2025-09-23T18:22:25.04Z" }, + { url = "https://files.pythonhosted.org/packages/82/94/8a0c68f808ae22f3cfa8208c60af03cab852d1b59f8ab4fe174cdbc8b17d/crosshair_tool-0.0.96-cp313-cp313-win_amd64.whl", hash = "sha256:11445b8c077c890507bed77007c4a73808d49b23178474004b855da3060e5966", size = 532275, upload-time = "2025-09-23T18:22:26.101Z" }, + { url = "https://files.pythonhosted.org/packages/23/7a/f7ebb8b883b389d9ff45de508d7f69241f9f68cdc42e475b1d42497b4c6e/crosshair_tool-0.0.96-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:43ca34251d445833bd4352ef0b19f29efa741d31730b92f72bf0500d85283704", size = 534237, upload-time = "2025-09-23T18:22:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/57/3f/ae5bd2308b8cfa957af4d8bad374a096ec4d4c97ca50ce2b103c96c94a7b/crosshair_tool-0.0.96-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7807e7eba3d88440dd93aa2828fb486c15982171ca65e08593037799dd8a4a81", size = 526362, upload-time = "2025-09-23T18:22:37.455Z" }, + { url = "https://files.pythonhosted.org/packages/ae/57/57b724dabcfa970d6ca7813a443303f9d316a2a5807a3844d62875082f28/crosshair_tool-0.0.96-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:91c9d6dfed8877318a1e48d138f22713eccbb4cbba571225aa35e629f0edf5a6", size = 527159, upload-time = "2025-09-23T18:22:38.897Z" }, + { url = "https://files.pythonhosted.org/packages/18/6d/a54842104b04b50c62c5f28245f89b8bbe3a669a71069393feef07924899/crosshair_tool-0.0.96-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e08bfa146aef87e49356a4fb6f0b1cfe4d2fad8d77d5a526b08199543e8504c", size = 550135, upload-time = "2025-09-23T18:22:40.281Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/90ca7f254a5a53a2ec74c2856c68f04d1f7120c3386ea370797f07dad529/crosshair_tool-0.0.96-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:001cdcb6e5554257aee05ce45b4d90f34ff8d415cb526ef27bd6f75ae1220996", size = 548986, upload-time = "2025-09-23T18:22:41.36Z" }, + { url = "https://files.pythonhosted.org/packages/40/95/747815025aeb8cfd8ef7b41788d9ddfda59f2bd66e238f44c2c1aea4841c/crosshair_tool-0.0.96-cp39-cp39-win32.whl", hash = "sha256:fb7bd90231aabb991a97f8f5b125a259f57756952461d338ec23ff53dcf06ed9", size = 529398, upload-time = "2025-09-23T18:22:42.409Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2e/cde7dab3acbf5a1536ffe9864d27d831e993a00a1822a5e40296bcd830ae/crosshair_tool-0.0.96-cp39-cp39-win_amd64.whl", hash = "sha256:e086f8c31fdd7f7e42c359ae10a2f2f67ac82fdbfce10edcc6f96da848f31ead", size = 530393, upload-time = "2025-09-23T18:22:43.567Z" }, ] [[package]] @@ -566,7 +593,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -846,79 +873,79 @@ wheels = [ [[package]] name = "libcst" -version = "1.8.4" +version = "1.8.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml", marker = "python_full_version < '3.13'" }, { name = "pyyaml-ft", marker = "python_full_version >= '3.13'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/e5/1ecfb0a74ef502d2f3ddc9fad51185a1d4e57d56aae3755073574b6f8237/libcst-1.8.4.tar.gz", hash = "sha256:f0f105d32c49baf712df2be360d496de67a2375bcf4e9707e643b7efc2f9a55a", size = 884416, upload-time = "2025-09-09T19:42:39.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/9e/91e416ecb9ddf598b2c3190ace4c13307ba989f38032513b532275f16c8f/libcst-1.8.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:114343271f70a79e6d08bc395f5dfa150227341fab646cc0a58e80550e7659b7", size = 2210145, upload-time = "2025-09-09T19:40:50.959Z" }, - { url = "https://files.pythonhosted.org/packages/0f/9f/76983c58fac06b3bddb2b35ad7170f6a5c1b8916feb5c5859e3aefd6bfcb/libcst-1.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b376ef7fa30bef611d4fb32af1da0e767b801b00322028a874ab3a441686b6a9", size = 2088387, upload-time = "2025-09-09T19:40:53.072Z" }, - { url = "https://files.pythonhosted.org/packages/b0/25/722c5dbc7dd9eeb9d76e467700b59b1c8a6b86fa662f2b86e541cb3813ab/libcst-1.8.4-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:9be5b1b7d416900ff9bcdb4945692e6252fdcbd95514e98439f81568568c9e02", size = 2231129, upload-time = "2025-09-09T19:40:54.758Z" }, - { url = "https://files.pythonhosted.org/packages/63/19/413b0dc42cc4622cd820727af2c055bc3fdb9cbda94e74567ac7b9a9637c/libcst-1.8.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e4c5055e255d12745c7cc60fb5fb31c0f82855864c15dc9ad33a44f829b92600", size = 2326300, upload-time = "2025-09-09T19:40:56.56Z" }, - { url = "https://files.pythonhosted.org/packages/00/84/7f8cd4dcf3af87e54dccd85cdf6629c484dd4638178288eb38ca80361f1d/libcst-1.8.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b1e570ba816da408b5ee40ac479b34e56d995bf32dcca6f0ddb3d69b08e77de", size = 2292604, upload-time = "2025-09-09T19:40:58.335Z" }, - { url = "https://files.pythonhosted.org/packages/5c/71/9ca65ba624b10fb527fc937e0abef6587557212a8fb5e39c8d11d61d079d/libcst-1.8.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65364c214251ed5720f3f6d0c4ef1338aac91ad4bbc5d30253eac21832b0943a", size = 2403338, upload-time = "2025-09-09T19:41:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/3d/07/744dea2a64fd443a240e12994bafe676e0ffc5f051f4be19232cacc85a41/libcst-1.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:a90c80e4d89222e11c7a734bc1b7f930bc2aba7750ad149bde1b136f839ea788", size = 2109017, upload-time = "2025-09-09T19:41:01.595Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f6/0550d5a9687083fcf03fa4f6370d201a9baed246373785b6d895bf0f8d3c/libcst-1.8.4-cp310-cp310-win_arm64.whl", hash = "sha256:2d71e7e5982776f78cca9102286bb0895ef6f7083f76c0c9bc5ba4e9e40aee38", size = 1997504, upload-time = "2025-09-09T19:41:03.492Z" }, - { url = "https://files.pythonhosted.org/packages/e7/59/88694011718d7c89d4841c872e28824f15cac4982ae9e34382a96ad01cf7/libcst-1.8.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e7baaa6f01b6b6ea4b28d60204fddc679a3cd56d312beee200bd5f8f9711f0b", size = 2209939, upload-time = "2025-09-09T19:41:05.309Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/da8b092e7df6ee0137c03a9f63211b90c2caee5fff217f4c38736ca209f8/libcst-1.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:259737faf90552a0589d95393dcaa3d3028be03ab3ea87478d46a1a4f922dd91", size = 2088322, upload-time = "2025-09-09T19:41:06.747Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6f/8c0e8ce8c20fbe27bc10ba8beefea860597ad3e795eefe76888986779d99/libcst-1.8.4-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a65e3c409ef16ae369600d085d23a3897d4fccf4fdcc09294a402c513ac35906", size = 2230405, upload-time = "2025-09-09T19:41:09.695Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bb/628e81f24e0ca72f885b45534b790a79bfc566cbb96ff3336b56b15d7e50/libcst-1.8.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fa870f34018c7241ee9227723cac0787599a2a8a2bfd53eacfbbe1ea1a272ae6", size = 2325810, upload-time = "2025-09-09T19:41:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/86/40/8df4d2263e6e390fab9e586e233c8bb8a3a6ed1a5edf0310c0be5cfa7331/libcst-1.8.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3eeba4edb40b2291c2460fe8d7e43f47e5fcc33f186675db5d364395adca3401", size = 2292581, upload-time = "2025-09-09T19:41:12.767Z" }, - { url = "https://files.pythonhosted.org/packages/de/1b/ac6462b28ea0c04c3422e4a3b9e37cab1071465fea58b74f2cbb8b5b0e08/libcst-1.8.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a5cd7beef667e5de3c5fb0ec387dc19aeda5cd4606ff541d0e8613bb3ef3b23", size = 2402928, upload-time = "2025-09-09T19:41:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a1/d4425c7e7e0a08b757c7037021a63d83882282e912fcd5cc311d74fb8911/libcst-1.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:3de575f0b5b466f2e9656b963f5848103cc518c6f3581902c6f430b07864584f", size = 2108878, upload-time = "2025-09-09T19:41:15.502Z" }, - { url = "https://files.pythonhosted.org/packages/10/71/1817af378b1fe31e7646d4c92472af79717410c2887d882ffd2e0a85b4b7/libcst-1.8.4-cp311-cp311-win_arm64.whl", hash = "sha256:2fcff2130824f2cb5f4fd9c4c74fb639c5f02bc4228654461f6dc6b1006f20c0", size = 1997686, upload-time = "2025-09-09T19:41:17.029Z" }, - { url = "https://files.pythonhosted.org/packages/b0/81/b8cb11f2c504af1ef163af6f601739faf52a1ab8bb76b7a3e649271b553e/libcst-1.8.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1d468514a21cf3444dc3f3a4b1effc6c05255c98cc79e02af394652d260139f0", size = 2201626, upload-time = "2025-09-09T19:41:18.542Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8e/871c6bcf9ed1043b7824ca7911dac0c031e4ab90c147387b8fae5a2a2db2/libcst-1.8.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:870a49df8575c11ea4f5319d54750f95d2d06370a263bd42d924a9cf23cf0cbe", size = 2082143, upload-time = "2025-09-09T19:41:19.956Z" }, - { url = "https://files.pythonhosted.org/packages/23/4f/b00db484fdffba45bc1f88cbc0f0f69149602402b07752f1fecf3ba6652f/libcst-1.8.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c9c775bc473225a0ad8422150fd9cf18ed2eebd7040996772937ac558f294d6c", size = 2229271, upload-time = "2025-09-09T19:41:21.334Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/5d5871c2e22f12569ee80e4b5ee0abe3e1e4b8c5ea2340c10e2ac7b7af97/libcst-1.8.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:27eeb16edb7dc0711d67e28bb8c0288e4147210aeb2434f08c16ac5db6b559e5", size = 2326063, upload-time = "2025-09-09T19:41:23.164Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7e/34904ae945d970fabe51974398982f9be4ca7a4debbf8f72bad82dc8d3a5/libcst-1.8.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e12101ef2a6e05b7610badb2bfa597379289f1408e305a8d19faacdb872f47", size = 2291055, upload-time = "2025-09-09T19:41:24.563Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/7aba1afc815824b93acffd8207e605f4d830bcea9703cf6c2d9f282ace94/libcst-1.8.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:69b672c1afac5fe00d689f585ba57ac5facc4632f39b977d4b3e4711571c76e2", size = 2402980, upload-time = "2025-09-09T19:41:25.938Z" }, - { url = "https://files.pythonhosted.org/packages/25/2b/c0303783b282c610f1ab8973d22d8805ea5376663c867b03d72b54da0ac9/libcst-1.8.4-cp312-cp312-win_amd64.whl", hash = "sha256:7832ee448fbdf18884a1f9af5fba1be6d5e98deb560514d92339fd6318aef651", size = 2110472, upload-time = "2025-09-09T19:41:27.306Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/4e1b62a7f824172ff9a6cd824935f3d8dddcfbc4fe63649b3de728a64495/libcst-1.8.4-cp312-cp312-win_arm64.whl", hash = "sha256:6840e4011b583e9b7a71c00e7ab4281aea7456877b3ea6ecedb68a39a000bc64", size = 1996000, upload-time = "2025-09-09T19:41:29.053Z" }, - { url = "https://files.pythonhosted.org/packages/c8/28/5cd9b6c207d74445d1d82c681139f0a0824a551d3cd0c5369166ce959f8e/libcst-1.8.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e8d5158f976a5ee140ad0d3391e1a1b84b2ce5da62f16e48feab4bc21b91967", size = 2201679, upload-time = "2025-09-09T19:41:30.748Z" }, - { url = "https://files.pythonhosted.org/packages/5d/40/9010635bb74b5b18b098f12f7be86dbe5d297fbf93ab143469e7d8e95ed8/libcst-1.8.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a179c712f38acb85e81d8949e80e05a422c92dcf5a00d8f4976f7e547a9f0916", size = 2082225, upload-time = "2025-09-09T19:41:32.474Z" }, - { url = "https://files.pythonhosted.org/packages/fc/70/a26857526ed23f2d18443d6504e29bc3880564191680e3c1aeb499f31a3d/libcst-1.8.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d130f3e2d40c5f48cbbc804710ddf5b4db9dd7c0118f3b35f109164a555860d2", size = 2229377, upload-time = "2025-09-09T19:41:33.892Z" }, - { url = "https://files.pythonhosted.org/packages/5a/12/290f530f2fed55ef5ae79d0df1db371cbbfaeb68ea207e177b74356de713/libcst-1.8.4-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a4270123c988e130cec94bfe1b54d34784a40b34b2d5ac0507720c1272bd3209", size = 2325074, upload-time = "2025-09-09T19:41:35.227Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e3/c3bce18cd90e1e51917700295511ea3fc25092786ecea9e16f098030554c/libcst-1.8.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea74c56cb11a1fdca9f8ab258965adce23e049ef525fdcc5c254a093e3de25cb", size = 2290710, upload-time = "2025-09-09T19:41:36.951Z" }, - { url = "https://files.pythonhosted.org/packages/87/6e/ccd5eadeab6737aef9ad762e22cab7f3c1f8365ec7c73d7928ff0f47ec62/libcst-1.8.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7fe97d432d95b6bcb1694a6d0fa7e07dde8fa687a637958126410ee2ced94b81", size = 2401652, upload-time = "2025-09-09T19:41:38.358Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0e/204ef502116d53ea3e9f8e50d23f648508828a8356f63ea822a3a798c086/libcst-1.8.4-cp313-cp313-win_amd64.whl", hash = "sha256:2c6d8f7087e9eaf005efde573f3f36d1d40366160155c195a6c4230d4c8a5839", size = 2110158, upload-time = "2025-09-09T19:41:39.773Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5c/aac5ea09d240d651990a27309732189f1c9cfa6103a1a80ade0531d79d04/libcst-1.8.4-cp313-cp313-win_arm64.whl", hash = "sha256:062e424042c36a102abd11d8e9e27ac6be68e1a934b0ecfc9fb8fea017240d2f", size = 1995791, upload-time = "2025-09-09T19:41:41.617Z" }, - { url = "https://files.pythonhosted.org/packages/19/1d/76f76e8ff59bb30a9a972df121a3e18e90080e4ab952d121d7b15ac99f7e/libcst-1.8.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:873dd4e8b896f7cb0e78118badda55ec1f42e9301a4a948cc438955ff3ae2257", size = 2188733, upload-time = "2025-09-09T19:41:43.068Z" }, - { url = "https://files.pythonhosted.org/packages/da/42/09c57e183673b810c456e1a671e5273e9827576fca48b3b528de0805063f/libcst-1.8.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:52c9376ba11ede5430e40aa205101dfc41202465103c6540f24591f898afb3d6", size = 2072316, upload-time = "2025-09-09T19:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e7/dfff4584d1b2349ae7877682842a99b2bc82f486157bd1bb85af1d719914/libcst-1.8.4-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:074a3b17e270237fb36d3b94d7492fb137cb74217674484ba25e015e8d3d8bdc", size = 2220187, upload-time = "2025-09-09T19:41:46.204Z" }, - { url = "https://files.pythonhosted.org/packages/0a/45/17c15295ade7fd98b690d3dee212f93d4dc3ba18a5bacc77a387a6083f70/libcst-1.8.4-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:846aad04bac624a42d182add526d019e417e6a2b8a4c0bf690d32f9e1f3075ff", size = 2314794, upload-time = "2025-09-09T19:41:47.651Z" }, - { url = "https://files.pythonhosted.org/packages/57/03/0a16d37cd5c087a4432cd4347cf73007c271558064b991e86329ec9defe5/libcst-1.8.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:93c76ab41d736b66d6fb3df32cd33184eed17666d7dc3ce047cf7ccdfe80b5b1", size = 2278846, upload-time = "2025-09-09T19:41:49.699Z" }, - { url = "https://files.pythonhosted.org/packages/82/fb/0569199ce76f090820e594aa5e718d3e78dc94e9987c30bbefb8b67ba75f/libcst-1.8.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f167bf83dce662c9b499f1ea078ec2f2fee138e80f7d7dbd59c89ed28dc935f", size = 2390213, upload-time = "2025-09-09T19:41:51.72Z" }, - { url = "https://files.pythonhosted.org/packages/23/68/18b9db68ce4cb198469d5e6d2049eba7b06e991da72c71981e55595a621a/libcst-1.8.4-cp313-cp313t-win_amd64.whl", hash = "sha256:43cbb6b41bc2c4785136f59a66692287d527aeb022789c4af44ad6e85b7b2baa", size = 2102430, upload-time = "2025-09-09T19:41:53.549Z" }, - { url = "https://files.pythonhosted.org/packages/0c/fe/e221aa7b0ffe38a71ed36cbe79e43ecf4daea29db67e12f3bb08488fe2e4/libcst-1.8.4-cp313-cp313t-win_arm64.whl", hash = "sha256:6cc8b7e33f6c4677e220dd7025e1e980da4d3f497b9b8ee0320e36dd54597f68", size = 1983337, upload-time = "2025-09-09T19:41:55.099Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c8/f0877d548713999266bf142b77a97b6eb20a8799d6b7ac2acb3edcf881f3/libcst-1.8.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d011d731c2e673fbd9c84794418230a913ae3c98fc86f27814612b6b6d53d26b", size = 2201314, upload-time = "2025-09-09T19:41:56.491Z" }, - { url = "https://files.pythonhosted.org/packages/37/10/9cba76916fc2ab953435decfb27c49a913e83c180f74941ab02dcf2ea03a/libcst-1.8.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a334dd11cdea34275df91c2ae9cc5933ec7e0ad5698264966708d637d110b627", size = 2081950, upload-time = "2025-09-09T19:41:57.854Z" }, - { url = "https://files.pythonhosted.org/packages/0b/27/aefa266d47bb54ee7ef7d5d54cd27f64a6009cf7c0bdf50f3b969dc26cc5/libcst-1.8.4-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:783f52b7c8d82046f0d93812f62a25eb82c3834f198e6cbfd5bb03ca68b593c8", size = 2229652, upload-time = "2025-09-09T19:41:59.706Z" }, - { url = "https://files.pythonhosted.org/packages/b3/c5/dbbda9e46849054bed9424bb40b8768fedb8d5519ffeb404ce9c1c1afa5e/libcst-1.8.4-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0352c7d662c89243e730a28edf41577f87e28649c18ee365dd373c5fbdab2434", size = 2325110, upload-time = "2025-09-09T19:42:01.507Z" }, - { url = "https://files.pythonhosted.org/packages/fd/65/01c1f9b278a5881930fa7d3f1d14a627e87efdd8dd4b20dd97da83bcc4f6/libcst-1.8.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb188ebd4114144e14f6beb5499e43bebd0ca3ce7f2beb20921d49138c67b814", size = 2290878, upload-time = "2025-09-09T19:42:03.011Z" }, - { url = "https://files.pythonhosted.org/packages/aa/3b/0ff0725891ff5d4ec4f19db8082153f5be759cd80d0c2ab6136927722701/libcst-1.8.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a718e5f6b398a07ca5d533e6593c1590d69fe65c539323281959733d6d541dd", size = 2401610, upload-time = "2025-09-09T19:42:04.519Z" }, - { url = "https://files.pythonhosted.org/packages/7c/42/3ce5dc72c61e239ce6a6544906bf64d354fa89014646ece71f4e88039f87/libcst-1.8.4-cp314-cp314-win_amd64.whl", hash = "sha256:fedfd33e5dda2200d582554e6476626d4706aa1fa2794bfb271879f8edff89b9", size = 2185637, upload-time = "2025-09-09T19:42:06.393Z" }, - { url = "https://files.pythonhosted.org/packages/27/f8/690c1afa28a8e6e3035789ab7354ac939ec181413944c415a22ee38b6b4d/libcst-1.8.4-cp314-cp314-win_arm64.whl", hash = "sha256:eff724c17df10e059915000eaf59f4e79998b66a7d35681e934a9a48667df931", size = 2074533, upload-time = "2025-09-09T19:42:07.829Z" }, - { url = "https://files.pythonhosted.org/packages/17/a8/11e767c8d5be5eb399e9fb88a4ad53bdb37568e14247b67d23c2da0dffeb/libcst-1.8.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:64cc34d74c9543b30ec3d7481dd644cb1bb3888076b486592d7fa0f22632f1c6", size = 2188850, upload-time = "2025-09-09T19:42:09.263Z" }, - { url = "https://files.pythonhosted.org/packages/4c/27/755c075f96046bd5ce90f8bbafdcb5ab54efdafe9d6100fd8f82514f15a8/libcst-1.8.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3ad7f0a32ddcdff00a3eddfd35cfd8485d9f357a32e4c67558476570199f808f", size = 2072358, upload-time = "2025-09-09T19:42:10.585Z" }, - { url = "https://files.pythonhosted.org/packages/20/fe/e581142fb173fbe0897ad28a6e10f781a22083b7efc00e7acfa15d5e6045/libcst-1.8.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2e156760fc741bbf2fa68f4e3b15f019e924ea852f02276d0a53b7375cf70445", size = 2219111, upload-time = "2025-09-09T19:42:12.083Z" }, - { url = "https://files.pythonhosted.org/packages/70/48/942ccd02e0faf9971194b99a485c74cba89644dfedd377c8cd6c7eccae36/libcst-1.8.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:fceb17616f1afe528c88243e3e7f78f84f0cc287463f04f3c1243e20a469e869", size = 2314223, upload-time = "2025-09-09T19:42:15.038Z" }, - { url = "https://files.pythonhosted.org/packages/c3/aa/824cb8dd28ec743c1698dfd37708933ce831bb6f5cba9d9a23c82c050f44/libcst-1.8.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5db0b484670aac7ea442213afaa9addb1de0d9540a34ad44d376bec12242bc3a", size = 2278232, upload-time = "2025-09-09T19:42:16.859Z" }, - { url = "https://files.pythonhosted.org/packages/79/14/02ced7d56d63e3bd55d5396179f91c5d00f0b1207ab560d7874160fd0134/libcst-1.8.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:929798ca38ea76a5056f725221d66c6923e749caa9fa7f4cc86e914a3698493d", size = 2390115, upload-time = "2025-09-09T19:42:19.312Z" }, - { url = "https://files.pythonhosted.org/packages/d1/5d/feb4c2ed2f339972f08355777a6aeedc6f5be66a59860734c2306c3111b6/libcst-1.8.4-cp314-cp314t-win_amd64.whl", hash = "sha256:e6f309c0f42e323c527d8c9007f583fd1668e45884208184a70644d916f27829", size = 2177267, upload-time = "2025-09-09T19:42:20.911Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/a5c1cf99e2bffe9e48375f858c1f06927b69f29821b639bb4e71788c3014/libcst-1.8.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4b1cbadd988fee59b25ea154708cfed99cfaf45f9685707be422ad736371a9fe", size = 2063063, upload-time = "2025-09-09T19:42:22.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/36/310fb5d1e47af98fa372dfcc8177b9acba2dbafecb7de925f8dc59f0dad1/libcst-1.8.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fbadca1bc31f696875c955080c407a40b2d1aa7f79ca174a65dcb0542a57db6c", size = 2210136, upload-time = "2025-09-09T19:42:24.258Z" }, - { url = "https://files.pythonhosted.org/packages/2c/97/4fffd988d3f31f423368e9088da0485c7c836fa53c2d7cece33e67fbb139/libcst-1.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d4111f971632e9ddf8191aeef4576595e18ef3fa7b3016bfe15a08fa8554df", size = 2088565, upload-time = "2025-09-09T19:42:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/d7/69/87ca6f5667ea77d6dbe41429da20070def6db83e3cd519539f9551402578/libcst-1.8.4-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f5bd0bcdd2a8da9dad47d36d71757d8ba87baf887ae6982e2cb8621846610c49", size = 2231358, upload-time = "2025-09-09T19:42:30.371Z" }, - { url = "https://files.pythonhosted.org/packages/84/e3/c98dce6059d2fea7dc3023b9c22d1a139af8804d89ae3d97ac87cf123ad9/libcst-1.8.4-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2e24d11a1be0b1791f7bace9d406f5a70b8691ef77be377b606950803de4657d", size = 2326166, upload-time = "2025-09-09T19:42:31.779Z" }, - { url = "https://files.pythonhosted.org/packages/49/ed/6aa1713b6213fa72d157293e55404beb1e79a600639fe27c7a0576d41ec9/libcst-1.8.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:14bda1e4ea0b04d3926d41f6dafbfd311a951b75a60fe0d79bb5a8249c1cef5b", size = 2292673, upload-time = "2025-09-09T19:42:33.127Z" }, - { url = "https://files.pythonhosted.org/packages/ec/11/4cdf02b7ea674fc13f3cf2e617e34b75631b54dadd49a77afdec1bb3aee9/libcst-1.8.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:056733760ba5ac1fd4cd518cddd5a43b3adbe2e0f6c7ce02532a114f7cd5d85b", size = 2403056, upload-time = "2025-09-09T19:42:34.725Z" }, - { url = "https://files.pythonhosted.org/packages/58/f3/c3741835b30555ba236dc748f122d6b95f4108fe96ae2bda6945ed4313aa/libcst-1.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:33664117fcb2913fdbd7de07a009193b660a16e7af18f7c1b4449e428f3b0f95", size = 2109173, upload-time = "2025-09-09T19:42:36.679Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ca/30510bad02223e9320f1c2bdb55a3c23896a1a091c6dda1b86e175485533/libcst-1.8.4-cp39-cp39-win_arm64.whl", hash = "sha256:b69e94625702825309fd9e50760e77a5a60bd1e7a8e039862c8dd3011a6e1530", size = 1997745, upload-time = "2025-09-09T19:42:38.115Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5c/55/ca4552d7fe79a91b2a7b4fa39991e8a45a17c8bfbcaf264597d95903c777/libcst-1.8.5.tar.gz", hash = "sha256:e72e1816eed63f530668e93a4c22ff1cf8b91ddce0ec53e597d3f6c53e103ec7", size = 884582, upload-time = "2025-09-26T05:29:44.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/61/92115569ba7d5ccf0bd74d33641d261d184a09a9ed58699a8463c44b79d5/libcst-1.8.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:373011a1a995c6201cf76c72ab598cedc27de9a5d665428620610f599bfc5f20", size = 2206397, upload-time = "2025-09-26T05:27:47.397Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2f/199d716d211b4938ba9a6cd0406b9c4fe36432a0e843230d6fab18d3ef67/libcst-1.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:774df1b40d338d245bb2d4e368ed99feb72a4642984125a5db62a3f4013a6e87", size = 2090397, upload-time = "2025-09-26T05:27:49.578Z" }, + { url = "https://files.pythonhosted.org/packages/f5/38/0e058e52bd6ac3436f45315a943b8a214b856f3fe0b1fcdb5ca65c3d7e66/libcst-1.8.5-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:08762c19eaf3d72162150ac0f0e1aa70378a10182ee539b8ecdf55c7f83b7f82", size = 2231629, upload-time = "2025-09-26T05:27:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7e/d458480534bd30f4f9800228e38b96d647fc5f59d10f92ae2ab4d1271e5b/libcst-1.8.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:54a50034c29d477fd3ceed2bcc02e17142b354e4039831246c32fde59281d116", size = 2294203, upload-time = "2025-09-26T05:27:53.039Z" }, + { url = "https://files.pythonhosted.org/packages/54/ea/1900b3896b4a1bccdbee2484490cf50a00045f759e721e5e9915e5b4a4cf/libcst-1.8.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:667ec0b245b8fa1e4afaa69ab4640ff124d4f5e7a480196fedde705db69b8c56", size = 2297115, upload-time = "2025-09-26T05:27:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f8/ab4b1e7d0be5273948bd1e8f4ae1c3072dc4bf69f5fda559e04b30408e1b/libcst-1.8.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3b7e5142768418094fb8f673e107f01cfdfa70b72d6c97749f3619e2e8beacb1", size = 2398228, upload-time = "2025-09-26T05:27:56.654Z" }, + { url = "https://files.pythonhosted.org/packages/da/fb/2f334f94c6f61f1a8116da5234b2977efc2b752e23c7a6df0dd712a4f248/libcst-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:4ad060e43bd3ba54b4fefcc5f619fc2480fd5a7dbec6768b598bfe0eb46e3da9", size = 2106562, upload-time = "2025-09-26T05:27:58.439Z" }, + { url = "https://files.pythonhosted.org/packages/c1/16/f281fd995028ab2dec978b3468b43e3d8a465b7d804c1df0b3f7da03284e/libcst-1.8.5-cp310-cp310-win_arm64.whl", hash = "sha256:985303bbc3c748c8fb71f994b56cc2806385b423acd53f5dd1cc191b3c2df6d3", size = 1992204, upload-time = "2025-09-26T05:28:00.539Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a0/4efb5b33c184f72554409516c73c8900909f87de528538d194b2cb5898ac/libcst-1.8.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dd5a292ce2b6410bc100aeac2b18ba3554fd8a8f6aa0ee6a9238bb4031c521ca", size = 2206056, upload-time = "2025-09-26T05:28:02.503Z" }, + { url = "https://files.pythonhosted.org/packages/26/b0/8b1dca00aebfc89f8e538212e5582548cedfc0b8f3aa4e73a815fe87bdfd/libcst-1.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f43915cd523a6967ba1dfe137627ed3804892005330c3bf53674a2ab4ff3dad", size = 2090132, upload-time = "2025-09-26T05:28:04.511Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/78ad030ca973f2c58fa58c3f30d94c2239473d3aba6c9dd1bdedd5047ddd/libcst-1.8.5-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9a756bd314b87b87dec9f0f900672c37719645b1c8bb2b53fe37b5b5fe7ee2c2", size = 2231559, upload-time = "2025-09-26T05:28:06.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/2ee78c01070c919de3d6736a06d1d9ecaedcbe1f367f4eee3c34ae5f801e/libcst-1.8.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:26e9d5e756447873eeda78441fa7d1fe640c0b526e5be2b6b7ee0c8f03c4665f", size = 2293973, upload-time = "2025-09-26T05:28:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/50/cf/ef4cb1c1b16f4bd32b0d7a5f01b18168fd833010a916bc062958dd6bcd8a/libcst-1.8.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b33ec61f62ff6122dc9c5bf1401bc8a9f9a2f0663ca15661d21d14d9dc4de0", size = 2297099, upload-time = "2025-09-26T05:28:10.4Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/ccd2e449f09c745ded6925804a6fe66f4c96ef82a0330de646becb8c6140/libcst-1.8.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a80e14836ecbdf5374c2c82cd5cd290abaa7290ecfafe9259d0615a1ebccb30c", size = 2398032, upload-time = "2025-09-26T05:28:12.124Z" }, + { url = "https://files.pythonhosted.org/packages/1f/16/277d0666e77d53d0061cb73327053b114f516ab7b36c9d4c71963fb5e806/libcst-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:588acde1588544b3bfe06069c118ee731e6712f323f26a026733f0ec4512596e", size = 2106472, upload-time = "2025-09-26T05:28:13.945Z" }, + { url = "https://files.pythonhosted.org/packages/bd/25/b1594abbec644a10b61ee1c1bab935ccc992a17b3880aa50234b9b4e9b06/libcst-1.8.5-cp311-cp311-win_arm64.whl", hash = "sha256:a8146f945f1eb46406fab676f86de3b7f88aca9e5d421f6366f7a63c8a950254", size = 1991976, upload-time = "2025-09-26T05:28:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/13/bb/c7abe0654fcf00292d6959256948ce4ae07785c4f65a45c3e25cc4637074/libcst-1.8.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c7733aba7b43239157661207b1e3a9f3711a7fc061a0eca6a33f0716fdfd21", size = 2196690, upload-time = "2025-09-26T05:28:17.839Z" }, + { url = "https://files.pythonhosted.org/packages/49/25/e7c02209e8ce66e7b75a66d132118f6f812a8b03cd31ee7d96de56c733a1/libcst-1.8.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b8c3cfbbf6049e3c587713652e4b3c88cfbf7df7878b2eeefaa8dd20a48dc607", size = 2082616, upload-time = "2025-09-26T05:28:19.794Z" }, + { url = "https://files.pythonhosted.org/packages/32/68/a4f49d99e3130256e225d639722440ba2682c12812a30ebd7ba64fd0fd31/libcst-1.8.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:31d86025d8997c853f85c4b5d494f04a157fb962e24f187b4af70c7755c9b27d", size = 2229037, upload-time = "2025-09-26T05:28:21.459Z" }, + { url = "https://files.pythonhosted.org/packages/b2/62/4fa21600a0bf3eb9f4d4f8bbb50ef120fb0b2990195eabba997b0b889566/libcst-1.8.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff9c535cfe99f0be79ac3024772b288570751fc69fc472b44fca12d1912d1561", size = 2292806, upload-time = "2025-09-26T05:28:23.033Z" }, + { url = "https://files.pythonhosted.org/packages/14/df/a01e8d54b62060698e37e3e28f77559ecb70c7b93ffee00d17e40221f419/libcst-1.8.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e8204607504563d3606bbaea2b9b04e0cef2b3bdc14c89171a702c1e09b9318a", size = 2294836, upload-time = "2025-09-26T05:28:24.937Z" }, + { url = "https://files.pythonhosted.org/packages/75/4f/c410e7f7ceda0558f688c1ca5dfb3a40ff8dfc527f8e6015fa749e11a650/libcst-1.8.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5e6cd3df72d47701b205fa3349ba8899566df82cef248c2fdf5f575d640419c4", size = 2396004, upload-time = "2025-09-26T05:28:26.582Z" }, + { url = "https://files.pythonhosted.org/packages/f0/07/bb77dcb94badad0ad3e5a1e992a4318dbdf40632eac3b5cf18299858ad7d/libcst-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:197c2f86dd0ca5c6464184ddef7f6440d64c8da39b78d16fc053da6701ed1209", size = 2107301, upload-time = "2025-09-26T05:28:28.235Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/e688e6d99d6920c3f97bf8bbaec33ac2c71a947730772a1d32dd899dbbf1/libcst-1.8.5-cp312-cp312-win_arm64.whl", hash = "sha256:c5ca109c9a81dff3d947dceba635a08f9c3dfeb7f61b0b824a175ef0a98ea69b", size = 1990870, upload-time = "2025-09-26T05:28:29.858Z" }, + { url = "https://files.pythonhosted.org/packages/b0/77/ca1d2499881c774121ebb7c78c22f371c179f18317961e1e529dafc1af52/libcst-1.8.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9e9563dcd754b65557ba9cdff9a5af32cfa5f007be0db982429580db45bfe", size = 2196687, upload-time = "2025-09-26T05:28:31.769Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1c/fdb7c226ad82fcf3b1bb19c24d8e895588a0c1fd2bc81e30792d041e15bc/libcst-1.8.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61d56839d237e9bf3310e6479ffaf6659f298940f0e0d2460ce71ee67a5375df", size = 2082639, upload-time = "2025-09-26T05:28:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/c6e89455483355971d13f6d71ad717624686b50558f7e2c12393c2c8e2f1/libcst-1.8.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b084769dcda2036265fc426eec5894c658af8d4b0e0d0255ab6bb78c8c9d6eb4", size = 2229202, upload-time = "2025-09-26T05:28:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/02/9c/3e4ce737a34c0ada15a35f51d0dbd8bf0ac0cef0c4560ddc0a8364e3f712/libcst-1.8.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c20384b8a4a7801b4416ef96173f1fbb7fafad7529edfdf151811ef70423118a", size = 2293220, upload-time = "2025-09-26T05:28:37.201Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/a68fcb3625b0c218c01aaefef9366f505654a1aa64af99cfe7ff7c97bf41/libcst-1.8.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:271b0b363972ff7d2b8116add13977e7c3b2668c7a424095851d548d222dab18", size = 2295146, upload-time = "2025-09-26T05:28:39.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/c3/f4b6edf204f919c6968eb2d111c338098aebbe3fb5d5d95aceacfcf65d9a/libcst-1.8.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ba728c7aee73b330f49f2df0f0b56b74c95302eeb78860f8d5ff0e0fc52c887", size = 2396597, upload-time = "2025-09-26T05:28:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/d0/94/b5cbe122db8f60e7e05bd56743f91d176f3da9b2101f8234e25bb3c5e493/libcst-1.8.5-cp313-cp313-win_amd64.whl", hash = "sha256:0abf0e87570cd3b06a8cafbb5378a9d1cbf12e4583dc35e0fff2255100da55a1", size = 2107479, upload-time = "2025-09-26T05:28:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/05/4d/5e47752c37b33ea6fd1fac76f62e2caa37a6f78d841338bb8fd3dcf51498/libcst-1.8.5-cp313-cp313-win_arm64.whl", hash = "sha256:757390c3cf0b45d7ae1d1d4070c839b082926e762e65eab144f37a63ad33b939", size = 1990992, upload-time = "2025-09-26T05:28:44.993Z" }, + { url = "https://files.pythonhosted.org/packages/88/df/d0eaaed2c402f945fd049b990c98242cb6eace640258e9f8d484206a9666/libcst-1.8.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f8934763389cd21ce3ed229b63b994b79dac8be7e84a9da144823f46bc1ffc5c", size = 2187746, upload-time = "2025-09-26T05:28:46.946Z" }, + { url = "https://files.pythonhosted.org/packages/19/05/ca62c80dc5f2cf26c2d5d1428612950c6f04df66f765ab0ca8b7d42b4ba1/libcst-1.8.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b873caf04862b6649a2a961fce847f7515ba882be02376a924732cf82c160861", size = 2072530, upload-time = "2025-09-26T05:28:48.451Z" }, + { url = "https://files.pythonhosted.org/packages/1a/38/34a5825bd87badaf8bc0725e5816d395f43ea2f8d1f3cb6982cccc70a1a2/libcst-1.8.5-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:50e095d18c4f76da0e03f25c50b52a2999acbcbe4598a3cf41842ee3c13b54f1", size = 2219819, upload-time = "2025-09-26T05:28:50.328Z" }, + { url = "https://files.pythonhosted.org/packages/74/ea/10407cc1c06231079f5ee6c5e2c2255a2c3f876a7a7f13af734f9bb6ee0e/libcst-1.8.5-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a3c967725cc3e8fa5c7251188d57d48eec8835f44c6b53f7523992bec595fa0", size = 2283011, upload-time = "2025-09-26T05:28:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/5b/fc/c4e4c03b4804ac78b8209e83a3c15e449aa68ddd0e602d5c2cc4b7e1b9ed/libcst-1.8.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eed454ab77f4b18100c41d8973b57069e503943ea4e5e5bbb660404976a0fe7a", size = 2283315, upload-time = "2025-09-26T05:28:53.33Z" }, + { url = "https://files.pythonhosted.org/packages/bb/39/75e07c2933b55815b71b1971e5388a24d1d1475631266251249eaed8af28/libcst-1.8.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:39130e59868b8fa49f6eeedd46f008d3456fc13ded57e1c85b211636eb6425f3", size = 2387279, upload-time = "2025-09-26T05:28:54.872Z" }, + { url = "https://files.pythonhosted.org/packages/04/44/0315fb0f2ee8913d209a5caf57932db8efb3f562dbcdc5fb157de92fb098/libcst-1.8.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a7b1cc3abfdba5ce36907f94f07e079528d4be52c07dfffa26f0e68eb1d25d45", size = 2098827, upload-time = "2025-09-26T05:28:56.877Z" }, + { url = "https://files.pythonhosted.org/packages/45/c2/1335fe9feb7d75526df454a8f9db77615460c69691c27af0a57621ca9e47/libcst-1.8.5-cp313-cp313t-win_arm64.whl", hash = "sha256:20354c4217e87afea936e9ea90c57fe0b2c5651f41b3ee59f5df8a53ab417746", size = 1979853, upload-time = "2025-09-26T05:28:58.408Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/4d961f15e7cc3f9924c4865158cf23de3cb1d9727be5bc5ec1f6b2e0e991/libcst-1.8.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f350ff2867b3075ba97a022de694f2747c469c25099216cef47b58caaee96314", size = 2196843, upload-time = "2025-09-26T05:29:00.64Z" }, + { url = "https://files.pythonhosted.org/packages/47/b5/706b51025218b31346335c8aa1e316e91dbd82b9bd60483a23842a59033b/libcst-1.8.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b95db09d04d125619a63f191c9534853656c4c76c303b8b4c5f950c8e610fba", size = 2082306, upload-time = "2025-09-26T05:29:02.498Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/53816b76257d9d149f074ac0b913be1c94d54fb07b3a77f3e11333659d36/libcst-1.8.5-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:60e62e966b45b7dee6f0ec0fd7687704d29be18ae670c5bc6c9c61a12ccf589f", size = 2230603, upload-time = "2025-09-26T05:29:04.123Z" }, + { url = "https://files.pythonhosted.org/packages/a6/06/4497c456ad0ace0f60a38f0935d6e080600532bcddeaf545443d4d7c4db2/libcst-1.8.5-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:7cbb330a352dde570059c73af7b7bbfaa84ae121f54d2ce46c5530351f57419d", size = 2293110, upload-time = "2025-09-26T05:29:05.685Z" }, + { url = "https://files.pythonhosted.org/packages/14/fc/9ef8cc7c0a9cca722b6f176cc82b5925dbcdfcee6e17cd6d3056d45af38e/libcst-1.8.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71b2b1ef2305cba051252342a1a4f8e94e6b8e95d7693a7c15a00ce8849ef722", size = 2296366, upload-time = "2025-09-26T05:29:07.451Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7e/799dac0cd086cc5dab3837ead9c72dd4e29a79323795dc52b2ebb3aac9a0/libcst-1.8.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0f504d06dfba909d1ba6a4acf60bfe3f22275444d6e0d07e472a5da4a209b0be", size = 2397188, upload-time = "2025-09-26T05:29:09.084Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5c/e4f32439818db04ea43b1d6de1d375dcdd5ff33b828864900c340f26436c/libcst-1.8.5-cp314-cp314-win_amd64.whl", hash = "sha256:c69d2b39e360dea5490ccb5dcf5957dcbb1067d27dc1f3f0787d4e287f7744e2", size = 2183599, upload-time = "2025-09-26T05:29:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f9/a457c3da610aef4b5f5c00f1feb67192594b77fb9dddab8f654161c1ea6f/libcst-1.8.5-cp314-cp314-win_arm64.whl", hash = "sha256:63405cb548b2d7b78531535a7819231e633b13d3dee3eb672d58f0f3322892ca", size = 2071025, upload-time = "2025-09-26T05:29:12.546Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b6/37abad6fc44df268cd8c2a903ddb2108bd8ac324ef000c2dfcb03d763a41/libcst-1.8.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8a5921105610f35921cc4db6fa5e68e941c6da20ce7f9f93b41b6c66b5481353", size = 2187762, upload-time = "2025-09-26T05:29:14.322Z" }, + { url = "https://files.pythonhosted.org/packages/b4/19/d1118c0b25612a3f50fb2c4b2010562fbf7e7df30ad821bab0aae9cf7e4f/libcst-1.8.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:abded10e8d92462fa982d19b064c6f24ed7ead81cf3c3b71011e9764cb12923d", size = 2072565, upload-time = "2025-09-26T05:29:16.37Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/f72515e2774234c4f92909222d762789cc4be2247ed4189bc0639ade1f8c/libcst-1.8.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dd7bdb14545c4b77a6c0eb39c86a76441fe833da800f6ca63e917e1273621029", size = 2219884, upload-time = "2025-09-26T05:29:18.118Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b8/b267b28cbb0cae19e8c7887cdeda72288ae1020d1c22b6c9955f065b296e/libcst-1.8.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6dc28d33ab8750a84c28b5625f7916846ecbecefd89bf75a5292a35644b6efbd", size = 2282790, upload-time = "2025-09-26T05:29:19.578Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/46f2b01bb6782dbc0f4e917ed029b1236278a5dc6d263e55ee986a83a88e/libcst-1.8.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:970b7164a71c65e13c961965f9677bbbbeb21ce2e7e6655294f7f774156391c4", size = 2283591, upload-time = "2025-09-26T05:29:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ca/3097729b5f6ab1d5e3a753492912d1d8b483a320421d3c0e9e26f1ecef0c/libcst-1.8.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd74c543770e6a61dcb8846c9689dfcce2ad686658896f77f3e21b6ce94bcb2e", size = 2386780, upload-time = "2025-09-26T05:29:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/4fc91968779b70429106797ddb2265a18b0026e17ec6ba805c34427d2fb9/libcst-1.8.5-cp314-cp314t-win_amd64.whl", hash = "sha256:3d8e80cd1ed6577166f0bab77357f819f12564c2ed82307612e2bcc93e684d72", size = 2174807, upload-time = "2025-09-26T05:29:24.799Z" }, + { url = "https://files.pythonhosted.org/packages/79/3c/db47e1cf0c98a13cbea2cb5611e7b6913ac5e63845b0e41ee7020b03f523/libcst-1.8.5-cp314-cp314t-win_arm64.whl", hash = "sha256:a026aaa19cb2acd8a4d9e2a215598b0a7e2c194bf4482eb9dec4d781ec6e10b2", size = 2059048, upload-time = "2025-09-26T05:29:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/ef/50/ccb84342efda2a9a813770c19f4cfdbf46a87dc684407fb99dbbf2664795/libcst-1.8.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:62d19557e9ca8c4d4969e4139f6678ee36beacce5a1dddbdb8f891e7fb867e84", size = 2206835, upload-time = "2025-09-26T05:29:30.424Z" }, + { url = "https://files.pythonhosted.org/packages/2f/de/631bb8d87fc4a78ee0c41ec709a02ac2d83e6241f91b9d65abb4d07a09fa/libcst-1.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd389a8a1da7cd48f47e72606153548de1a4aae7914c6af6302bcd3095bc592d", size = 2090767, upload-time = "2025-09-26T05:29:32.288Z" }, + { url = "https://files.pythonhosted.org/packages/49/2c/1513790ef9f66cd72504aa3eb566d19cc078b140febc6bd2066cf5f01aea/libcst-1.8.5-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b7de38b9b6c24825d028be70ec12745d268a763d2fb89344f65db749be13733f", size = 2231284, upload-time = "2025-09-26T05:29:33.895Z" }, + { url = "https://files.pythonhosted.org/packages/de/f9/90c1ae8aec8ceb0073aaa97e1a84b7c157be9195492381b174076421f12b/libcst-1.8.5-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:395aa10f34b91c952098eb69fc461f17fcda4e1dc4ac462c3bdff2d4dfbb92e7", size = 2294295, upload-time = "2025-09-26T05:29:35.818Z" }, + { url = "https://files.pythonhosted.org/packages/a1/19/80445490e091084ac133121303a0c6c1e7d8fede79f94d8d54ddb18618ce/libcst-1.8.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e431d331f4296090325dc22bc4e9e4a32aff08d51ee31053b7efff16faf87fc", size = 2297074, upload-time = "2025-09-26T05:29:37.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/6d24af90811a0f7b96f14c04353c577ea82db9c5f53e8bc621638726c7b9/libcst-1.8.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3e409c0784d0950b16555799bfa108199209b7df159d84ebe443fe08aa0ba8f6", size = 2398139, upload-time = "2025-09-26T05:29:39.36Z" }, + { url = "https://files.pythonhosted.org/packages/68/26/e8d849f04f7dd023afc4674ef861a60a5e60030e7458a4bda6882de98248/libcst-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:aaad71a6079eb9ebe84f982bb0ccebd4f5010f5f18c6324690b73efc4427b3fa", size = 2106297, upload-time = "2025-09-26T05:29:41.007Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fc/e462c07faae288ec4275447027206bf733406452e4e7fb2a808a10957908/libcst-1.8.5-cp39-cp39-win_arm64.whl", hash = "sha256:0ade64fbbeae77b5f2cf0b4fd62afa51c56f51fa026eb1f1627e65ec6d2e38d7", size = 1991921, upload-time = "2025-09-26T05:29:42.638Z" }, ] [[package]] @@ -986,118 +1013,142 @@ wheels = [ [[package]] name = "lxml" -version = "6.0.1" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/bd/f9d01fd4132d81c6f43ab01983caea69ec9614b913c290a26738431a015d/lxml-6.0.1.tar.gz", hash = "sha256:2b3a882ebf27dd026df3801a87cf49ff791336e0f94b0fad195db77e01240690", size = 4070214, upload-time = "2025-08-22T10:37:53.525Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/06/29693634ad5fc8ae0bab6723ba913c821c780614eea9ab9ebb5b2105d0e4/lxml-6.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b38e20c578149fdbba1fd3f36cb1928a3aaca4b011dfd41ba09d11fb396e1b9", size = 8381164, upload-time = "2025-08-22T10:31:55.164Z" }, - { url = "https://files.pythonhosted.org/packages/97/e0/69d4113afbda9441f0e4d5574d9336535ead6a0608ee6751b3db0832ade0/lxml-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a052cbd013b7140bbbb38a14e2329b6192478344c99097e378c691b7119551", size = 4553444, upload-time = "2025-08-22T10:31:57.86Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3d/8fa1dbf48a3ea0d6c646f0129bef89a5ecf9a1cfe935e26e07554261d728/lxml-6.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:21344d29c82ca8547ea23023bb8e7538fa5d4615a1773b991edf8176a870c1ea", size = 4997433, upload-time = "2025-08-22T10:32:00.058Z" }, - { url = "https://files.pythonhosted.org/packages/2c/52/a48331a269900488b886d527611ab66238cddc6373054a60b3c15d4cefb2/lxml-6.0.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa8f130f4b2dc94baa909c17bb7994f0268a2a72b9941c872e8e558fd6709050", size = 5155765, upload-time = "2025-08-22T10:32:01.951Z" }, - { url = "https://files.pythonhosted.org/packages/33/3b/8f6778a6fb9d30a692db2b1f5a9547dfcb674b27b397e1d864ca797486b1/lxml-6.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4588806a721552692310ebe9f90c17ac6c7c5dac438cd93e3d74dd60531c3211", size = 5066508, upload-time = "2025-08-22T10:32:04.358Z" }, - { url = "https://files.pythonhosted.org/packages/42/15/c9364f23fa89ef2d3dbb896912aa313108820286223cfa833a0a9e183c9e/lxml-6.0.1-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:8466faa66b0353802fb7c054a400ac17ce2cf416e3ad8516eadeff9cba85b741", size = 5405401, upload-time = "2025-08-22T10:32:06.741Z" }, - { url = "https://files.pythonhosted.org/packages/04/af/11985b0d47786161ddcdc53dc06142dc863b81a38da7f221c7b997dd5d4b/lxml-6.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50b5e54f6a9461b1e9c08b4a3420415b538d4773bd9df996b9abcbfe95f4f1fd", size = 5287651, upload-time = "2025-08-22T10:32:08.697Z" }, - { url = "https://files.pythonhosted.org/packages/6a/42/74b35ccc9ef1bb53f0487a4dace5ff612f1652d27faafe91ada7f7b9ee60/lxml-6.0.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:6f393e10685b37f15b1daef8aa0d734ec61860bb679ec447afa0001a31e7253f", size = 4771036, upload-time = "2025-08-22T10:32:10.579Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/b934534f83561ad71fb64ba1753992e836ea73776cfb56fc0758dbb46bdf/lxml-6.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:07038c62fd0fe2743e2f5326f54d464715373c791035d7dda377b3c9a5d0ad77", size = 5109855, upload-time = "2025-08-22T10:32:13.012Z" }, - { url = "https://files.pythonhosted.org/packages/6c/26/d833a56ec8ca943b696f3a7a1e54f97cfb63754c951037de5e222c011f3b/lxml-6.0.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7a44a5fb1edd11b3a65c12c23e1049c8ae49d90a24253ff18efbcb6aa042d012", size = 4798088, upload-time = "2025-08-22T10:32:15.128Z" }, - { url = "https://files.pythonhosted.org/packages/3f/cb/601aa274c7cda51d0cc84a13d9639096c1191de9d9adf58f6c195d4822a2/lxml-6.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a57d9eb9aadf311c9e8785230eec83c6abb9aef2adac4c0587912caf8f3010b8", size = 5313252, upload-time = "2025-08-22T10:32:17.44Z" }, - { url = "https://files.pythonhosted.org/packages/76/4e/e079f7b324e6d5f83007f30855448646e1cba74b5c30da1a081df75eba89/lxml-6.0.1-cp310-cp310-win32.whl", hash = "sha256:d877874a31590b72d1fa40054b50dc33084021bfc15d01b3a661d85a302af821", size = 3611251, upload-time = "2025-08-22T10:32:19.223Z" }, - { url = "https://files.pythonhosted.org/packages/65/0a/da298d7a96316c75ae096686de8d036d814ec3b72c7d643a2c226c364168/lxml-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c43460f4aac016ee0e156bfa14a9de9b3e06249b12c228e27654ac3996a46d5b", size = 4031884, upload-time = "2025-08-22T10:32:21.054Z" }, - { url = "https://files.pythonhosted.org/packages/0f/65/d7f61082fecf4543ab084e8bd3d4b9be0c1a0c83979f1fa2258e2a7987fb/lxml-6.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:615bb6c73fed7929e3a477a3297a797892846b253d59c84a62c98bdce3849a0a", size = 3679487, upload-time = "2025-08-22T10:32:22.781Z" }, - { url = "https://files.pythonhosted.org/packages/29/c8/262c1d19339ef644cdc9eb5aad2e85bd2d1fa2d7c71cdef3ede1a3eed84d/lxml-6.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6acde83f7a3d6399e6d83c1892a06ac9b14ea48332a5fbd55d60b9897b9570a", size = 8422719, upload-time = "2025-08-22T10:32:24.848Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d4/1b0afbeb801468a310642c3a6f6704e53c38a4a6eb1ca6faea013333e02f/lxml-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d21c9cacb6a889cbb8eeb46c77ef2c1dd529cde10443fdeb1de847b3193c541", size = 4575763, upload-time = "2025-08-22T10:32:27.057Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c1/8db9b5402bf52ceb758618313f7423cd54aea85679fcf607013707d854a8/lxml-6.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:847458b7cd0d04004895f1fb2cca8e7c0f8ec923c49c06b7a72ec2d48ea6aca2", size = 4943244, upload-time = "2025-08-22T10:32:28.847Z" }, - { url = "https://files.pythonhosted.org/packages/e7/78/838e115358dd2369c1c5186080dd874a50a691fb5cd80db6afe5e816e2c6/lxml-6.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1dc13405bf315d008fe02b1472d2a9d65ee1c73c0a06de5f5a45e6e404d9a1c0", size = 5081725, upload-time = "2025-08-22T10:32:30.666Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b6/bdcb3a3ddd2438c5b1a1915161f34e8c85c96dc574b0ef3be3924f36315c/lxml-6.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f540c229a8c0a770dcaf6d5af56a5295e0fc314fc7ef4399d543328054bcea", size = 5021238, upload-time = "2025-08-22T10:32:32.49Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/1bfb96185dc1a64c7c6fbb7369192bda4461952daa2025207715f9968205/lxml-6.0.1-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:d2f73aef768c70e8deb8c4742fca4fd729b132fda68458518851c7735b55297e", size = 5343744, upload-time = "2025-08-22T10:32:34.385Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ae/df3ea9ebc3c493b9c6bdc6bd8c554ac4e147f8d7839993388aab57ec606d/lxml-6.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7f4066b85a4fa25ad31b75444bd578c3ebe6b8ed47237896341308e2ce923c3", size = 5223477, upload-time = "2025-08-22T10:32:36.256Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/65e1e33600542c08bc03a4c5c9c306c34696b0966a424a3be6ffec8038ed/lxml-6.0.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0cce65db0cd8c750a378639900d56f89f7d6af11cd5eda72fde054d27c54b8ce", size = 4676626, upload-time = "2025-08-22T10:32:38.793Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/ee3ed8f3a60e9457d7aea46542d419917d81dbfd5700fe64b2a36fb5ef61/lxml-6.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c372d42f3eee5844b69dcab7b8d18b2f449efd54b46ac76970d6e06b8e8d9a66", size = 5066042, upload-time = "2025-08-22T10:32:41.134Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b9/8394538e7cdbeb3bfa36bc74924be1a4383e0bb5af75f32713c2c4aa0479/lxml-6.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2e2b0e042e1408bbb1c5f3cfcb0f571ff4ac98d8e73f4bf37c5dd179276beedd", size = 4724714, upload-time = "2025-08-22T10:32:43.94Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/3ef7da1ea2a73976c1a5a311d7cde5d379234eec0968ee609517714940b4/lxml-6.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc73bb8640eadd66d25c5a03175de6801f63c535f0f3cf50cac2f06a8211f420", size = 5247376, upload-time = "2025-08-22T10:32:46.263Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/0980016f124f00c572cba6f4243e13a8e80650843c66271ee692cddf25f3/lxml-6.0.1-cp311-cp311-win32.whl", hash = "sha256:7c23fd8c839708d368e406282d7953cee5134f4592ef4900026d84566d2b4c88", size = 3609499, upload-time = "2025-08-22T10:32:48.156Z" }, - { url = "https://files.pythonhosted.org/packages/b1/08/28440437521f265eff4413eb2a65efac269c4c7db5fd8449b586e75d8de2/lxml-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:2516acc6947ecd3c41a4a4564242a87c6786376989307284ddb115f6a99d927f", size = 4036003, upload-time = "2025-08-22T10:32:50.662Z" }, - { url = "https://files.pythonhosted.org/packages/7b/dc/617e67296d98099213a505d781f04804e7b12923ecd15a781a4ab9181992/lxml-6.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:cb46f8cfa1b0334b074f40c0ff94ce4d9a6755d492e6c116adb5f4a57fb6ad96", size = 3679662, upload-time = "2025-08-22T10:32:52.739Z" }, - { url = "https://files.pythonhosted.org/packages/b0/a9/82b244c8198fcdf709532e39a1751943a36b3e800b420adc739d751e0299/lxml-6.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c03ac546adaabbe0b8e4a15d9ad815a281afc8d36249c246aecf1aaad7d6f200", size = 8422788, upload-time = "2025-08-22T10:32:56.612Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8d/1ed2bc20281b0e7ed3e6c12b0a16e64ae2065d99be075be119ba88486e6d/lxml-6.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33b862c7e3bbeb4ba2c96f3a039f925c640eeba9087a4dc7a572ec0f19d89392", size = 4593547, upload-time = "2025-08-22T10:32:59.016Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/d7fd3af95b72a3493bf7fbe842a01e339d8f41567805cecfecd5c71aa5ee/lxml-6.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a3ec1373f7d3f519de595032d4dcafae396c29407cfd5073f42d267ba32440d", size = 4948101, upload-time = "2025-08-22T10:33:00.765Z" }, - { url = "https://files.pythonhosted.org/packages/9d/51/4e57cba4d55273c400fb63aefa2f0d08d15eac021432571a7eeefee67bed/lxml-6.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03b12214fb1608f4cffa181ec3d046c72f7e77c345d06222144744c122ded870", size = 5108090, upload-time = "2025-08-22T10:33:03.108Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6e/5f290bc26fcc642bc32942e903e833472271614e24d64ad28aaec09d5dae/lxml-6.0.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:207ae0d5f0f03b30f95e649a6fa22aa73f5825667fee9c7ec6854d30e19f2ed8", size = 5021791, upload-time = "2025-08-22T10:33:06.972Z" }, - { url = "https://files.pythonhosted.org/packages/13/d4/2e7551a86992ece4f9a0f6eebd4fb7e312d30f1e372760e2109e721d4ce6/lxml-6.0.1-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:32297b09ed4b17f7b3f448de87a92fb31bb8747496623483788e9f27c98c0f00", size = 5358861, upload-time = "2025-08-22T10:33:08.967Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/cb49d727fc388bf5fd37247209bab0da11697ddc5e976ccac4826599939e/lxml-6.0.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e18224ea241b657a157c85e9cac82c2b113ec90876e01e1f127312006233756", size = 5652569, upload-time = "2025-08-22T10:33:10.815Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b8/66c1ef8c87ad0f958b0a23998851e610607c74849e75e83955d5641272e6/lxml-6.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a07a994d3c46cd4020c1ea566345cf6815af205b1e948213a4f0f1d392182072", size = 5252262, upload-time = "2025-08-22T10:33:12.673Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ef/131d3d6b9590e64fdbb932fbc576b81fcc686289da19c7cb796257310e82/lxml-6.0.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:2287fadaa12418a813b05095485c286c47ea58155930cfbd98c590d25770e225", size = 4710309, upload-time = "2025-08-22T10:33:14.952Z" }, - { url = "https://files.pythonhosted.org/packages/bc/3f/07f48ae422dce44902309aa7ed386c35310929dc592439c403ec16ef9137/lxml-6.0.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b4e597efca032ed99f418bd21314745522ab9fa95af33370dcee5533f7f70136", size = 5265786, upload-time = "2025-08-22T10:33:16.721Z" }, - { url = "https://files.pythonhosted.org/packages/11/c7/125315d7b14ab20d9155e8316f7d287a4956098f787c22d47560b74886c4/lxml-6.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9696d491f156226decdd95d9651c6786d43701e49f32bf23715c975539aa2b3b", size = 5062272, upload-time = "2025-08-22T10:33:18.478Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c3/51143c3a5fc5168a7c3ee626418468ff20d30f5a59597e7b156c1e61fba8/lxml-6.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e4e3cd3585f3c6f87cdea44cda68e692cc42a012f0131d25957ba4ce755241a7", size = 4786955, upload-time = "2025-08-22T10:33:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/11/86/73102370a420ec4529647b31c4a8ce8c740c77af3a5fae7a7643212d6f6e/lxml-6.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:45cbc92f9d22c28cd3b97f8d07fcefa42e569fbd587dfdac76852b16a4924277", size = 5673557, upload-time = "2025-08-22T10:33:22.282Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2d/aad90afaec51029aef26ef773b8fd74a9e8706e5e2f46a57acd11a421c02/lxml-6.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f8c9bcfd2e12299a442fba94459adf0b0d001dbc68f1594439bfa10ad1ecb74b", size = 5254211, upload-time = "2025-08-22T10:33:24.15Z" }, - { url = "https://files.pythonhosted.org/packages/63/01/c9e42c8c2d8b41f4bdefa42ab05448852e439045f112903dd901b8fbea4d/lxml-6.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e9dc2b9f1586e7cd77753eae81f8d76220eed9b768f337dc83a3f675f2f0cf9", size = 5275817, upload-time = "2025-08-22T10:33:26.007Z" }, - { url = "https://files.pythonhosted.org/packages/bc/1f/962ea2696759abe331c3b0e838bb17e92224f39c638c2068bf0d8345e913/lxml-6.0.1-cp312-cp312-win32.whl", hash = "sha256:987ad5c3941c64031f59c226167f55a04d1272e76b241bfafc968bdb778e07fb", size = 3610889, upload-time = "2025-08-22T10:33:28.169Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/22c86a990b51b44442b75c43ecb2f77b8daba8c4ba63696921966eac7022/lxml-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:abb05a45394fd76bf4a60c1b7bec0e6d4e8dfc569fc0e0b1f634cd983a006ddc", size = 4010925, upload-time = "2025-08-22T10:33:29.874Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/dc0c73325e5eb94ef9c9d60dbb5dcdcb2e7114901ea9509735614a74e75a/lxml-6.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:c4be29bce35020d8579d60aa0a4e95effd66fcfce31c46ffddf7e5422f73a299", size = 3671922, upload-time = "2025-08-22T10:33:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/cd757eeec4548e6652eff50b944079d18ce5f8182d2b2cf514e125e8fbcb/lxml-6.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:485eda5d81bb7358db96a83546949c5fe7474bec6c68ef3fa1fb61a584b00eea", size = 8405139, upload-time = "2025-08-22T10:33:34.09Z" }, - { url = "https://files.pythonhosted.org/packages/ff/99/0290bb86a7403893f5e9658490c705fcea103b9191f2039752b071b4ef07/lxml-6.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d12160adea318ce3d118f0b4fbdff7d1225c75fb7749429541b4d217b85c3f76", size = 4585954, upload-time = "2025-08-22T10:33:36.294Z" }, - { url = "https://files.pythonhosted.org/packages/88/a7/4bb54dd1e626342a0f7df6ec6ca44fdd5d0e100ace53acc00e9a689ead04/lxml-6.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48c8d335d8ab72f9265e7ba598ae5105a8272437403f4032107dbcb96d3f0b29", size = 4944052, upload-time = "2025-08-22T10:33:38.19Z" }, - { url = "https://files.pythonhosted.org/packages/71/8d/20f51cd07a7cbef6214675a8a5c62b2559a36d9303fe511645108887c458/lxml-6.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:405e7cf9dbdbb52722c231e0f1257214202dfa192327fab3de45fd62e0554082", size = 5098885, upload-time = "2025-08-22T10:33:40.035Z" }, - { url = "https://files.pythonhosted.org/packages/5a/63/efceeee7245d45f97d548e48132258a36244d3c13c6e3ddbd04db95ff496/lxml-6.0.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:299a790d403335a6a057ade46f92612ebab87b223e4e8c5308059f2dc36f45ed", size = 5017542, upload-time = "2025-08-22T10:33:41.896Z" }, - { url = "https://files.pythonhosted.org/packages/57/5d/92cb3d3499f5caba17f7933e6be3b6c7de767b715081863337ced42eb5f2/lxml-6.0.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:48da704672f6f9c461e9a73250440c647638cc6ff9567ead4c3b1f189a604ee8", size = 5347303, upload-time = "2025-08-22T10:33:43.868Z" }, - { url = "https://files.pythonhosted.org/packages/69/f8/606fa16a05d7ef5e916c6481c634f40870db605caffed9d08b1a4fb6b989/lxml-6.0.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21e364e1bb731489e3f4d51db416f991a5d5da5d88184728d80ecfb0904b1d68", size = 5641055, upload-time = "2025-08-22T10:33:45.784Z" }, - { url = "https://files.pythonhosted.org/packages/b3/01/15d5fc74ebb49eac4e5df031fbc50713dcc081f4e0068ed963a510b7d457/lxml-6.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bce45a2c32032afddbd84ed8ab092130649acb935536ef7a9559636ce7ffd4a", size = 5242719, upload-time = "2025-08-22T10:33:48.089Z" }, - { url = "https://files.pythonhosted.org/packages/42/a5/1b85e2aaaf8deaa67e04c33bddb41f8e73d07a077bf9db677cec7128bfb4/lxml-6.0.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:fa164387ff20ab0e575fa909b11b92ff1481e6876835014e70280769920c4433", size = 4717310, upload-time = "2025-08-22T10:33:49.852Z" }, - { url = "https://files.pythonhosted.org/packages/42/23/f3bb1292f55a725814317172eeb296615db3becac8f1a059b53c51fc1da8/lxml-6.0.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7587ac5e000e1594e62278422c5783b34a82b22f27688b1074d71376424b73e8", size = 5254024, upload-time = "2025-08-22T10:33:52.22Z" }, - { url = "https://files.pythonhosted.org/packages/b4/be/4d768f581ccd0386d424bac615d9002d805df7cc8482ae07d529f60a3c1e/lxml-6.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:57478424ac4c9170eabf540237125e8d30fad1940648924c058e7bc9fb9cf6dd", size = 5055335, upload-time = "2025-08-22T10:33:54.041Z" }, - { url = "https://files.pythonhosted.org/packages/40/07/ed61d1a3e77d1a9f856c4fab15ee5c09a2853fb7af13b866bb469a3a6d42/lxml-6.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:09c74afc7786c10dd6afaa0be2e4805866beadc18f1d843cf517a7851151b499", size = 4784864, upload-time = "2025-08-22T10:33:56.382Z" }, - { url = "https://files.pythonhosted.org/packages/01/37/77e7971212e5c38a55431744f79dff27fd751771775165caea096d055ca4/lxml-6.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7fd70681aeed83b196482d42a9b0dc5b13bab55668d09ad75ed26dff3be5a2f5", size = 5657173, upload-time = "2025-08-22T10:33:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/32/a3/e98806d483941cd9061cc838b1169626acef7b2807261fbe5e382fcef881/lxml-6.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:10a72e456319b030b3dd900df6b1f19d89adf06ebb688821636dc406788cf6ac", size = 5245896, upload-time = "2025-08-22T10:34:00.586Z" }, - { url = "https://files.pythonhosted.org/packages/07/de/9bb5a05e42e8623bf06b4638931ea8c8f5eb5a020fe31703abdbd2e83547/lxml-6.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0fa45fb5f55111ce75b56c703843b36baaf65908f8b8d2fbbc0e249dbc127ed", size = 5267417, upload-time = "2025-08-22T10:34:02.719Z" }, - { url = "https://files.pythonhosted.org/packages/f2/43/c1cb2a7c67226266c463ef8a53b82d42607228beb763b5fbf4867e88a21f/lxml-6.0.1-cp313-cp313-win32.whl", hash = "sha256:01dab65641201e00c69338c9c2b8a0f2f484b6b3a22d10779bb417599fae32b5", size = 3610051, upload-time = "2025-08-22T10:34:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/34/96/6a6c3b8aa480639c1a0b9b6faf2a63fb73ab79ffcd2a91cf28745faa22de/lxml-6.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:bdf8f7c8502552d7bff9e4c98971910a0a59f60f88b5048f608d0a1a75e94d1c", size = 4009325, upload-time = "2025-08-22T10:34:06.24Z" }, - { url = "https://files.pythonhosted.org/packages/8c/66/622e8515121e1fd773e3738dae71b8df14b12006d9fb554ce90886689fd0/lxml-6.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6aeca75959426b9fd8d4782c28723ba224fe07cfa9f26a141004210528dcbe2", size = 3670443, upload-time = "2025-08-22T10:34:07.974Z" }, - { url = "https://files.pythonhosted.org/packages/38/e3/b7eb612ce07abe766918a7e581ec6a0e5212352194001fd287c3ace945f0/lxml-6.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:29b0e849ec7030e3ecb6112564c9f7ad6881e3b2375dd4a0c486c5c1f3a33859", size = 8426160, upload-time = "2025-08-22T10:34:10.154Z" }, - { url = "https://files.pythonhosted.org/packages/35/8f/ab3639a33595cf284fe733c6526da2ca3afbc5fd7f244ae67f3303cec654/lxml-6.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:02a0f7e629f73cc0be598c8b0611bf28ec3b948c549578a26111b01307fd4051", size = 4589288, upload-time = "2025-08-22T10:34:12.972Z" }, - { url = "https://files.pythonhosted.org/packages/2c/65/819d54f2e94d5c4458c1db8c1ccac9d05230b27c1038937d3d788eb406f9/lxml-6.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:beab5e54de016e730875f612ba51e54c331e2fa6dc78ecf9a5415fc90d619348", size = 4964523, upload-time = "2025-08-22T10:34:15.474Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4a/d4a74ce942e60025cdaa883c5a4478921a99ce8607fc3130f1e349a83b28/lxml-6.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a08aefecd19ecc4ebf053c27789dd92c87821df2583a4337131cf181a1dffa", size = 5101108, upload-time = "2025-08-22T10:34:17.348Z" }, - { url = "https://files.pythonhosted.org/packages/cb/48/67f15461884074edd58af17b1827b983644d1fae83b3d909e9045a08b61e/lxml-6.0.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c8fa7e177649470bc3dcf7eae6bee1e4984aaee496b9ccbf30e97ac4127fa2", size = 5053498, upload-time = "2025-08-22T10:34:19.232Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d4/ec1bf1614828a5492f4af0b6a9ee2eb3e92440aea3ac4fa158e5228b772b/lxml-6.0.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:5d08e0f1af6916267bb7eff21c09fa105620f07712424aaae09e8cb5dd4164d1", size = 5351057, upload-time = "2025-08-22T10:34:21.143Z" }, - { url = "https://files.pythonhosted.org/packages/65/2b/c85929dacac08821f2100cea3eb258ce5c8804a4e32b774f50ebd7592850/lxml-6.0.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9705cdfc05142f8c38c97a61bd3a29581ceceb973a014e302ee4a73cc6632476", size = 5671579, upload-time = "2025-08-22T10:34:23.528Z" }, - { url = "https://files.pythonhosted.org/packages/d0/36/cf544d75c269b9aad16752fd9f02d8e171c5a493ca225cb46bb7ba72868c/lxml-6.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74555e2da7c1636e30bff4e6e38d862a634cf020ffa591f1f63da96bf8b34772", size = 5250403, upload-time = "2025-08-22T10:34:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e8/83dbc946ee598fd75fdeae6151a725ddeaab39bb321354a9468d4c9f44f3/lxml-6.0.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:e38b5f94c5a2a5dadaddd50084098dfd005e5a2a56cd200aaf5e0a20e8941782", size = 4696712, upload-time = "2025-08-22T10:34:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/f4/72/889c633b47c06205743ba935f4d1f5aa4eb7f0325d701ed2b0540df1b004/lxml-6.0.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5ec101a92ddacb4791977acfc86c1afd624c032974bfb6a21269d1083c9bc49", size = 5268177, upload-time = "2025-08-22T10:34:29.804Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b6/f42a21a1428479b66ea0da7bd13e370436aecaff0cfe93270c7e165bd2a4/lxml-6.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5c17e70c82fd777df586c12114bbe56e4e6f823a971814fd40dec9c0de518772", size = 5094648, upload-time = "2025-08-22T10:34:31.703Z" }, - { url = "https://files.pythonhosted.org/packages/51/b0/5f8c1e8890e2ee1c2053c2eadd1cb0e4b79e2304e2912385f6ca666f48b1/lxml-6.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:45fdd0415a0c3d91640b5d7a650a8f37410966a2e9afebb35979d06166fd010e", size = 4745220, upload-time = "2025-08-22T10:34:33.595Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f9/820b5125660dae489ca3a21a36d9da2e75dd6b5ffe922088f94bbff3b8a0/lxml-6.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d417eba28981e720a14fcb98f95e44e7a772fe25982e584db38e5d3b6ee02e79", size = 5692913, upload-time = "2025-08-22T10:34:35.482Z" }, - { url = "https://files.pythonhosted.org/packages/23/8e/a557fae9eec236618aecf9ff35fec18df41b6556d825f3ad6017d9f6e878/lxml-6.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8e5d116b9e59be7934febb12c41cce2038491ec8fdb743aeacaaf36d6e7597e4", size = 5259816, upload-time = "2025-08-22T10:34:37.482Z" }, - { url = "https://files.pythonhosted.org/packages/fa/fd/b266cfaab81d93a539040be699b5854dd24c84e523a1711ee5f615aa7000/lxml-6.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c238f0d0d40fdcb695c439fe5787fa69d40f45789326b3bb6ef0d61c4b588d6e", size = 5276162, upload-time = "2025-08-22T10:34:39.507Z" }, - { url = "https://files.pythonhosted.org/packages/25/6c/6f9610fbf1de002048e80585ea4719591921a0316a8565968737d9f125ca/lxml-6.0.1-cp314-cp314-win32.whl", hash = "sha256:537b6cf1c5ab88cfd159195d412edb3e434fee880f206cbe68dff9c40e17a68a", size = 3669595, upload-time = "2025-08-22T10:34:41.783Z" }, - { url = "https://files.pythonhosted.org/packages/72/a5/506775e3988677db24dc75a7b03e04038e0b3d114ccd4bccea4ce0116c15/lxml-6.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:911d0a2bb3ef3df55b3d97ab325a9ca7e438d5112c102b8495321105d25a441b", size = 4079818, upload-time = "2025-08-22T10:34:44.04Z" }, - { url = "https://files.pythonhosted.org/packages/0a/44/9613f300201b8700215856e5edd056d4e58dd23368699196b58877d4408b/lxml-6.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:2834377b0145a471a654d699bdb3a2155312de492142ef5a1d426af2c60a0a31", size = 3753901, upload-time = "2025-08-22T10:34:45.799Z" }, - { url = "https://files.pythonhosted.org/packages/04/e7/8b1c778d0ea244079a081358f7bef91408f430d67ec8f1128c9714b40a6a/lxml-6.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:edb975280633a68d0988b11940834ce2b0fece9f5278297fc50b044cb713f0e1", size = 8387609, upload-time = "2025-08-22T10:36:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/e4/97/af75a865b0314c8f2bd5594662a8580fe7ad46e506bfad203bf632ace69a/lxml-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4c5acb9bc22f2026bbd0ecbfdb890e9b3e5b311b992609d35034706ad111b5d", size = 4557206, upload-time = "2025-08-22T10:36:56.811Z" }, - { url = "https://files.pythonhosted.org/packages/29/40/f3ab2e07b60196100cc00a1559715f10a5d980eba5e568069db0897108cc/lxml-6.0.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47ab1aff82a95a07d96c1eff4eaebec84f823e0dfb4d9501b1fbf9621270c1d3", size = 5001564, upload-time = "2025-08-22T10:36:59.479Z" }, - { url = "https://files.pythonhosted.org/packages/da/66/0d1e19e8ec32bad8fca5145128efd830f180cd0a46f4d3b3197ffadae025/lxml-6.0.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:faa7233bdb7a4365e2411a665d034c370ac82798a926e65f76c26fbbf0fd14b7", size = 5159268, upload-time = "2025-08-22T10:37:02.084Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e93e485184a9265b2da964964f8a2f0f22a75504c27241937177b1cbe1ca/lxml-6.0.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c71a0ce0e08c7e11e64895c720dc7752bf064bfecd3eb2c17adcd7bfa8ffb22c", size = 5069618, upload-time = "2025-08-22T10:37:05.275Z" }, - { url = "https://files.pythonhosted.org/packages/ba/95/83e9ef69fa527495166ea83da46865659968f09f2a27b6ad85eee9459177/lxml-6.0.1-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:57744270a512a93416a149f8b6ea1dbbbee127f5edcbcd5adf28e44b6ff02f33", size = 5408879, upload-time = "2025-08-22T10:37:07.52Z" }, - { url = "https://files.pythonhosted.org/packages/bb/84/036366ca92c348f5f582ab24537d9016b5587685bea4986b3625b9c5b4e9/lxml-6.0.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e89d977220f7b1f0c725ac76f5c65904193bd4c264577a3af9017de17560ea7e", size = 5291262, upload-time = "2025-08-22T10:37:09.768Z" }, - { url = "https://files.pythonhosted.org/packages/e8/6a/edf19356c65597db9d84cc6442f1f83efb6fbc6615d700defc409c213646/lxml-6.0.1-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:0c8f7905f1971c2c408badf49ae0ef377cc54759552bcf08ae7a0a8ed18999c2", size = 4775119, upload-time = "2025-08-22T10:37:12.078Z" }, - { url = "https://files.pythonhosted.org/packages/06/e5/2461c902f3c6b493945122c72817e202b28d0d57b75afe30d048c330afa7/lxml-6.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ea27626739e82f2be18cbb1aff7ad59301c723dc0922d9a00bc4c27023f16ab7", size = 5115347, upload-time = "2025-08-22T10:37:14.222Z" }, - { url = "https://files.pythonhosted.org/packages/5a/89/77ba6c34fb3117bf8c306faeed969220c80016ecdf4eb4c485224c3c1a31/lxml-6.0.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21300d8c1bbcc38925aabd4b3c2d6a8b09878daf9e8f2035f09b5b002bcddd66", size = 4800640, upload-time = "2025-08-22T10:37:16.886Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f0/a94cf22539276c240f17b92213cef2e0476297d7a489bc08aad57df75b49/lxml-6.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:021497a94907c5901cd49d24b5b0fdd18d198a06611f5ce26feeb67c901b92f2", size = 5316865, upload-time = "2025-08-22T10:37:19.385Z" }, - { url = "https://files.pythonhosted.org/packages/83/a5/be1ffae7efa7d2a1a0d9e95cccd5b8bec9b4aa9a8175624ba6cfc5fbcd98/lxml-6.0.1-cp39-cp39-win32.whl", hash = "sha256:620869f2a3ec1475d000b608024f63259af8d200684de380ccb9650fbc14d1bb", size = 3613293, upload-time = "2025-08-22T10:37:21.881Z" }, - { url = "https://files.pythonhosted.org/packages/89/61/150e6ed573db558b8aadd5e23d391e7361730608a29058d0791b171f2cba/lxml-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:afae3a15889942426723839a3cf56dab5e466f7d873640a7a3c53abc671e2387", size = 4034539, upload-time = "2025-08-22T10:37:23.784Z" }, - { url = "https://files.pythonhosted.org/packages/9f/fc/f6624e88171b3fd3dfd4c3f4bbd577a5315ce1247a7c0c5fa7238d825dc5/lxml-6.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:2719e42acda8f3444a0d88204fd90665116dda7331934da4d479dd9296c33ce2", size = 3682596, upload-time = "2025-08-22T10:37:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/ae/61/ad51fbecaf741f825d496947b19d8aea0dcd323fdc2be304e93ce59f66f0/lxml-6.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0abfbaf4ebbd7fd33356217d317b6e4e2ef1648be6a9476a52b57ffc6d8d1780", size = 3891543, upload-time = "2025-08-22T10:37:27.849Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7f/310bef082cc69d0db46a8b9d8ca5f4a8fb41e1c5d299ef4ca5f391c4f12d/lxml-6.0.1-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ebbf2d9775be149235abebdecae88fe3b3dd06b1797cd0f6dffe6948e85309d", size = 4215518, upload-time = "2025-08-22T10:37:30.065Z" }, - { url = "https://files.pythonhosted.org/packages/86/cc/dc5833def5998c783500666468df127d6d919e8b9678866904e5680b0b13/lxml-6.0.1-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a389e9f11c010bd30531325805bbe97bdf7f728a73d0ec475adef57ffec60547", size = 4325058, upload-time = "2025-08-22T10:37:32.125Z" }, - { url = "https://files.pythonhosted.org/packages/1b/dc/bdd4d413844b5348134444d64911f6f34b211f8b778361946d07623fc904/lxml-6.0.1-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f5cf2addfbbe745251132c955ad62d8519bb4b2c28b0aa060eca4541798d86e", size = 4267739, upload-time = "2025-08-22T10:37:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/d9/14/e60e9d46972603753824eb7bea06fbe4153c627cc0f7110111253b7c9fc5/lxml-6.0.1-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1b60a3287bf33a2a54805d76b82055bcc076e445fd539ee9ae1fe85ed373691", size = 4410303, upload-time = "2025-08-22T10:37:36.002Z" }, - { url = "https://files.pythonhosted.org/packages/42/fa/268c9be8c69a418b8106e096687aba2b1a781fb6fc1b3f04955fac2be2b9/lxml-6.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f7bbfb0751551a8786915fc6b615ee56344dacc1b1033697625b553aefdd9837", size = 3516013, upload-time = "2025-08-22T10:37:38.739Z" }, - { url = "https://files.pythonhosted.org/packages/41/37/41961f53f83ded57b37e65e4f47d1c6c6ef5fd02cb1d6ffe028ba0efa7d4/lxml-6.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b556aaa6ef393e989dac694b9c95761e32e058d5c4c11ddeef33f790518f7a5e", size = 3903412, upload-time = "2025-08-22T10:37:40.758Z" }, - { url = "https://files.pythonhosted.org/packages/3d/47/8631ea73f3dc776fb6517ccde4d5bd5072f35f9eacbba8c657caa4037a69/lxml-6.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:64fac7a05ebb3737b79fd89fe5a5b6c5546aac35cfcfd9208eb6e5d13215771c", size = 4224810, upload-time = "2025-08-22T10:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b8/39ae30ca3b1516729faeef941ed84bf8f12321625f2644492ed8320cb254/lxml-6.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:038d3c08babcfce9dc89aaf498e6da205efad5b7106c3b11830a488d4eadf56b", size = 4329221, upload-time = "2025-08-22T10:37:45.223Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/048dea6cdfc7a72d40ae8ed7e7d23cf4a6b6a6547b51b492a3be50af0e80/lxml-6.0.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:445f2cee71c404ab4259bc21e20339a859f75383ba2d7fb97dfe7c163994287b", size = 4270228, upload-time = "2025-08-22T10:37:47.276Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d4/c2b46e432377c45d611ae2f669aa47971df1586c1a5240675801d0f02bac/lxml-6.0.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e352d8578e83822d70bea88f3d08b9912528e4c338f04ab707207ab12f4b7aac", size = 4416077, upload-time = "2025-08-22T10:37:49.822Z" }, - { url = "https://files.pythonhosted.org/packages/b6/db/8f620f1ac62cf32554821b00b768dd5957ac8e3fd051593532be5b40b438/lxml-6.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:51bd5d1a9796ca253db6045ab45ca882c09c071deafffc22e06975b7ace36300", size = 3518127, upload-time = "2025-08-22T10:37:51.66Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/8a/f8192a08237ef2fb1b19733f709db88a4c43bc8ab8357f01cb41a27e7f6a/lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388", size = 8590589, upload-time = "2025-09-22T04:00:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/27bcd07ae17ff5e5536e8d88f4c7d581b48963817a13de11f3ac3329bfa2/lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153", size = 4629671, upload-time = "2025-09-22T04:00:15.411Z" }, + { url = "https://files.pythonhosted.org/packages/02/5a/a7d53b3291c324e0b6e48f3c797be63836cc52156ddf8f33cd72aac78866/lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31", size = 4999961, upload-time = "2025-09-22T04:00:17.619Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/d465e9b89df1761674d8672bb3e4ae2c47033b01ec243964b6e334c6743f/lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9", size = 5157087, upload-time = "2025-09-22T04:00:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/38/3073cd7e3e8dfc3ba3c3a139e33bee3a82de2bfb0925714351ad3d255c13/lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8", size = 5067620, upload-time = "2025-09-22T04:00:21.877Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d3/1e001588c5e2205637b08985597827d3827dbaaece16348c8822bfe61c29/lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba", size = 5406664, upload-time = "2025-09-22T04:00:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/20/cf/cab09478699b003857ed6ebfe95e9fb9fa3d3c25f1353b905c9b73cfb624/lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c", size = 5289397, upload-time = "2025-09-22T04:00:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/02a2d0c38ac9a8b9f9e5e1bbd3f24b3f426044ad618b552e9549ee91bd63/lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c", size = 4772178, upload-time = "2025-09-22T04:00:27.602Z" }, + { url = "https://files.pythonhosted.org/packages/56/87/e1ceadcc031ec4aa605fe95476892d0b0ba3b7f8c7dcdf88fdeff59a9c86/lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321", size = 5358148, upload-time = "2025-09-22T04:00:29.323Z" }, + { url = "https://files.pythonhosted.org/packages/fe/13/5bb6cf42bb228353fd4ac5f162c6a84fd68a4d6f67c1031c8cf97e131fc6/lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1", size = 5112035, upload-time = "2025-09-22T04:00:31.061Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e2/ea0498552102e59834e297c5c6dff8d8ded3db72ed5e8aad77871476f073/lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34", size = 4799111, upload-time = "2025-09-22T04:00:33.11Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9e/8de42b52a73abb8af86c66c969b3b4c2a96567b6ac74637c037d2e3baa60/lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a", size = 5351662, upload-time = "2025-09-22T04:00:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/28/a2/de776a573dfb15114509a37351937c367530865edb10a90189d0b4b9b70a/lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c", size = 5314973, upload-time = "2025-09-22T04:00:37.086Z" }, + { url = "https://files.pythonhosted.org/packages/50/a0/3ae1b1f8964c271b5eec91db2043cf8c6c0bce101ebb2a633b51b044db6c/lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b", size = 3611953, upload-time = "2025-09-22T04:00:39.224Z" }, + { url = "https://files.pythonhosted.org/packages/d1/70/bd42491f0634aad41bdfc1e46f5cff98825fb6185688dc82baa35d509f1a/lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0", size = 4032695, upload-time = "2025-09-22T04:00:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d0/05c6a72299f54c2c561a6c6cbb2f512e047fca20ea97a05e57931f194ac4/lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5", size = 3680051, upload-time = "2025-09-22T04:00:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, + { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, + { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, + { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, + { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, + { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, + { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, + { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, + { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, + { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, + { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, + { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, + { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, + { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, + { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/dd13c74fad495957374c8a81c932f4874d3dca5aa0db9e4369f06a399718/lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694", size = 8602363, upload-time = "2025-09-22T04:03:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f4/edb9d47dce464b5dd044d35775ee794364935b93ab6226c95e199118890d/lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90", size = 4634995, upload-time = "2025-09-22T04:04:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/f2/d80c97b6ed83a99bc24b2b29919d5e618af5322df6d3aa61064093712309/lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62", size = 5003737, upload-time = "2025-09-22T04:04:02.98Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f1/18b750f79f8889b9109b24749f23ac137870b4f685edc4be54be0ff2c730/lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444", size = 5160821, upload-time = "2025-09-22T04:04:04.854Z" }, + { url = "https://files.pythonhosted.org/packages/cf/88/2b6a415dbad411c3e9c092128eb7db06054d2d9460aa56676d17ee4f4fd5/lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad", size = 5070959, upload-time = "2025-09-22T04:04:07.042Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d0/5354afaa0f2e53625e5f96f6bd049a4875c3ab79d96d6c4871dd1f4a98c4/lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48", size = 5410267, upload-time = "2025-09-22T04:04:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/51/63/10dea35a01291dc529fa9d6ba204ea627a1c77b7fbb180d404f6cc4dd2fd/lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93", size = 5292990, upload-time = "2025-09-22T04:04:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/37/58/51ef422d8bec58db600b3552e5f2d870ec01ffacf11d98689c42ffdcbf7f/lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c", size = 4776318, upload-time = "2025-09-22T04:04:14.22Z" }, + { url = "https://files.pythonhosted.org/packages/77/97/3f797820e82e3a58a19bc51068b40f3b9ab7d0934ba6e5ba6b147b618319/lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c", size = 5360191, upload-time = "2025-09-22T04:04:16.236Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/a9306a8ab122e2f5dfbf4f71fb09beeadca26b0c275708432bbc33f40edc/lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf", size = 5116114, upload-time = "2025-09-22T04:04:18.594Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/2118a1685277b9fa8726ec7ee903db55aa300dcea3d406a220cbe3710953/lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6", size = 4801704, upload-time = "2025-09-22T04:04:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e8/d5be34da2059dc9a4ff8643fd6ad3f8234a27b2a44831b7fff58c4dbb3e3/lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84", size = 5355451, upload-time = "2025-09-22T04:04:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/61/84/5aebc8e150d5bf488815ea2d8798c7ff509cc37b5725caa3c1f11bdd3245/lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb", size = 5318630, upload-time = "2025-09-22T04:04:24.301Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/629ae603c1c17fb7adc9df2bc21aa5ac96afb84001700b13c1f038f3118c/lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f", size = 3614032, upload-time = "2025-09-22T04:04:26.158Z" }, + { url = "https://files.pythonhosted.org/packages/71/de/07b7b1249acbecbf48f7e42c3ce87a657af6ff38e30f12a1ad81f16010f2/lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304", size = 4035311, upload-time = "2025-09-22T04:04:28.413Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/02c4c55b281606f3c8e118300e16a9fcf5f3462cc46ce740ed0b82fc3f1b/lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa", size = 3683462, upload-time = "2025-09-22T04:04:30.399Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9c/780c9a8fce3f04690b374f72f41306866b0400b9d0fdf3e17aaa37887eed/lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6", size = 3939264, upload-time = "2025-09-22T04:04:32.892Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/1ab260c00adf645d8bf7dec7f920f744b032f69130c681302821d5debea6/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba", size = 4216435, upload-time = "2025-09-22T04:04:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/f2/37/565f3b3d7ffede22874b6d86be1a1763d00f4ea9fc5b9b6ccb11e4ec8612/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5", size = 4325913, upload-time = "2025-09-22T04:04:37.205Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/f3a1b169b2fb9d03467e2e3c0c752ea30e993be440a068b125fc7dd248b0/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4", size = 4269357, upload-time = "2025-09-22T04:04:39.322Z" }, + { url = "https://files.pythonhosted.org/packages/77/a2/585a28fe3e67daa1cf2f06f34490d556d121c25d500b10082a7db96e3bcd/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d", size = 4412295, upload-time = "2025-09-22T04:04:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/a57dd8bcebd7c69386c20263830d4fa72d27e6b72a229ef7a48e88952d9a/lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d", size = 3516913, upload-time = "2025-09-22T04:04:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] [[package]] @@ -1165,7 +1216,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, @@ -1173,45 +1224,45 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" }, - { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" }, - { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" }, - { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" }, - { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" }, - { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, - { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, - { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, - { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, - { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, - { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, - { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, - { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, - { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, - { url = "https://files.pythonhosted.org/packages/29/cb/673e3d34e5d8de60b3a61f44f80150a738bff568cd6b7efb55742a605e98/mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9", size = 10992466, upload-time = "2025-07-31T07:53:57.574Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d0/fe1895836eea3a33ab801561987a10569df92f2d3d4715abf2cfeaa29cb2/mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99", size = 10117638, upload-time = "2025-07-31T07:53:34.256Z" }, - { url = "https://files.pythonhosted.org/packages/97/f3/514aa5532303aafb95b9ca400a31054a2bd9489de166558c2baaeea9c522/mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8", size = 11915673, upload-time = "2025-07-31T07:52:59.361Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c3/c0805f0edec96fe8e2c048b03769a6291523d509be8ee7f56ae922fa3882/mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8", size = 12649022, upload-time = "2025-07-31T07:53:45.92Z" }, - { url = "https://files.pythonhosted.org/packages/45/3e/d646b5a298ada21a8512fa7e5531f664535a495efa672601702398cea2b4/mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259", size = 12895536, upload-time = "2025-07-31T07:53:06.17Z" }, - { url = "https://files.pythonhosted.org/packages/14/55/e13d0dcd276975927d1f4e9e2ec4fd409e199f01bdc671717e673cc63a22/mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d", size = 9512564, upload-time = "2025-07-31T07:53:12.346Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" }, + { url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" }, + { url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, ] [[package]] @@ -1542,7 +1593,7 @@ wheels = [ [[package]] name = "posthog" -version = "6.7.4" +version = "6.7.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, @@ -1552,9 +1603,9 @@ dependencies = [ { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/40/d7f585e09e47f492ebaeb8048a8e2ce5d9f49a3896856a7a975cbc1484fa/posthog-6.7.4.tar.gz", hash = "sha256:2bfa74f321ac18efe4a48a256d62034a506ca95477af7efa32292ed488a742c5", size = 118209, upload-time = "2025-09-05T15:29:21.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/ce/11d6fa30ab517018796e1d675498992da585479e7079770ec8fa99a61561/posthog-6.7.6.tar.gz", hash = "sha256:ee5c5ad04b857d96d9b7a4f715e23916a2f206bfcf25e5a9d328a3d27664b0d3", size = 119129, upload-time = "2025-09-22T18:11:12.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/95/e795059ef73d480a7f11f1be201087f65207509525920897fb514a04914c/posthog-6.7.4-py3-none-any.whl", hash = "sha256:7f1872c53ec7e9a29b088a5a1ad03fa1be3b871d10d70c8bf6c2dafb91beaac5", size = 136409, upload-time = "2025-09-05T15:29:19.995Z" }, + { url = "https://files.pythonhosted.org/packages/de/84/586422d8861b5391c8414360b10f603c0b7859bb09ad688e64430ed0df7b/posthog-6.7.6-py3-none-any.whl", hash = "sha256:b09a7e65a042ec416c28874b397d3accae412a80a8b0ef3fa686fbffc99e4d4b", size = 137348, upload-time = "2025-09-22T18:11:10.807Z" }, ] [[package]] @@ -1605,7 +1656,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1613,9 +1664,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, ] [[package]] @@ -1807,55 +1858,75 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, - { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, - { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, - { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] @@ -2098,28 +2169,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" }, - { url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" }, - { url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" }, - { url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" }, - { url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" }, - { url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" }, - { url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" }, - { url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" }, - { url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" }, - { url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" }, +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/df/8d7d8c515d33adfc540e2edf6c6021ea1c5a58a678d8cfce9fae59aabcab/ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff", size = 5416417, upload-time = "2025-09-25T14:54:09.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/84/5716a7fa4758e41bf70e603e13637c42cfb9dbf7ceb07180211b9bbf75ef/ruff-0.13.2-py3-none-linux_armv6l.whl", hash = "sha256:3796345842b55f033a78285e4f1641078f902020d8450cade03aad01bffd81c3", size = 12343254, upload-time = "2025-09-25T14:53:27.784Z" }, + { url = "https://files.pythonhosted.org/packages/9b/77/c7042582401bb9ac8eff25360e9335e901d7a1c0749a2b28ba4ecb239991/ruff-0.13.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ff7e4dda12e683e9709ac89e2dd436abf31a4d8a8fc3d89656231ed808e231d2", size = 13040891, upload-time = "2025-09-25T14:53:31.38Z" }, + { url = "https://files.pythonhosted.org/packages/c6/15/125a7f76eb295cb34d19c6778e3a82ace33730ad4e6f28d3427e134a02e0/ruff-0.13.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75e9d2a2fafd1fdd895d0e7e24b44355984affdde1c412a6f6d3f6e16b22d46", size = 12243588, upload-time = "2025-09-25T14:53:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/9e/eb/0093ae04a70f81f8be7fd7ed6456e926b65d238fc122311293d033fdf91e/ruff-0.13.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cceac74e7bbc53ed7d15d1042ffe7b6577bf294611ad90393bf9b2a0f0ec7cb6", size = 12491359, upload-time = "2025-09-25T14:53:35.892Z" }, + { url = "https://files.pythonhosted.org/packages/43/fe/72b525948a6956f07dad4a6f122336b6a05f2e3fd27471cea612349fedb9/ruff-0.13.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae3f469b5465ba6d9721383ae9d49310c19b452a161b57507764d7ef15f4b07", size = 12162486, upload-time = "2025-09-25T14:53:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e3/0fac422bbbfb2ea838023e0d9fcf1f30183d83ab2482800e2cb892d02dfe/ruff-0.13.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f8f9e3cd6714358238cd6626b9d43026ed19c0c018376ac1ef3c3a04ffb42d8", size = 13871203, upload-time = "2025-09-25T14:53:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/6b/82/b721c8e3ec5df6d83ba0e45dcf00892c4f98b325256c42c38ef136496cbf/ruff-0.13.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c6ed79584a8f6cbe2e5d7dbacf7cc1ee29cbdb5df1172e77fbdadc8bb85a1f89", size = 14929635, upload-time = "2025-09-25T14:53:43.953Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/ad56faf6daa507b83079a1ad7a11694b87d61e6bf01c66bd82b466f21821/ruff-0.13.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aed130b2fde049cea2019f55deb939103123cdd191105f97a0599a3e753d61b0", size = 14338783, upload-time = "2025-09-25T14:53:46.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/77/ad1d9156db8f99cd01ee7e29d74b34050e8075a8438e589121fcd25c4b08/ruff-0.13.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1887c230c2c9d65ed1b4e4cfe4d255577ea28b718ae226c348ae68df958191aa", size = 13355322, upload-time = "2025-09-25T14:53:48.164Z" }, + { url = "https://files.pythonhosted.org/packages/64/8b/e87cfca2be6f8b9f41f0bb12dc48c6455e2d66df46fe61bb441a226f1089/ruff-0.13.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bcb10276b69b3cfea3a102ca119ffe5c6ba3901e20e60cf9efb53fa417633c3", size = 13354427, upload-time = "2025-09-25T14:53:50.486Z" }, + { url = "https://files.pythonhosted.org/packages/7f/df/bf382f3fbead082a575edb860897287f42b1b3c694bafa16bc9904c11ed3/ruff-0.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:afa721017aa55a555b2ff7944816587f1cb813c2c0a882d158f59b832da1660d", size = 13537637, upload-time = "2025-09-25T14:53:52.887Z" }, + { url = "https://files.pythonhosted.org/packages/51/70/1fb7a7c8a6fc8bd15636288a46e209e81913b87988f26e1913d0851e54f4/ruff-0.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1dbc875cf3720c64b3990fef8939334e74cb0ca65b8dbc61d1f439201a38101b", size = 12340025, upload-time = "2025-09-25T14:53:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/27/1e5b3f1c23ca5dd4106d9d580e5c13d9acb70288bff614b3d7b638378cc9/ruff-0.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939a1b2a960e9742e9a347e5bbc9b3c3d2c716f86c6ae273d9cbd64f193f22", size = 12133449, upload-time = "2025-09-25T14:53:57.089Z" }, + { url = "https://files.pythonhosted.org/packages/2d/09/b92a5ccee289f11ab128df57d5911224197d8d55ef3bd2043534ff72ca54/ruff-0.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:50e2d52acb8de3804fc5f6e2fa3ae9bdc6812410a9e46837e673ad1f90a18736", size = 13051369, upload-time = "2025-09-25T14:53:59.124Z" }, + { url = "https://files.pythonhosted.org/packages/89/99/26c9d1c7d8150f45e346dc045cc49f23e961efceb4a70c47dea0960dea9a/ruff-0.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3196bc13ab2110c176b9a4ae5ff7ab676faaa1964b330a1383ba20e1e19645f2", size = 13523644, upload-time = "2025-09-25T14:54:01.622Z" }, + { url = "https://files.pythonhosted.org/packages/f7/00/e7f1501e81e8ec290e79527827af1d88f541d8d26151751b46108978dade/ruff-0.13.2-py3-none-win32.whl", hash = "sha256:7c2a0b7c1e87795fec3404a485096bcd790216c7c146a922d121d8b9c8f1aaac", size = 12245990, upload-time = "2025-09-25T14:54:03.647Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bd/d9f33a73de84fafd0146c6fba4f497c4565fe8fa8b46874b8e438869abc2/ruff-0.13.2-py3-none-win_amd64.whl", hash = "sha256:17d95fb32218357c89355f6f6f9a804133e404fc1f65694372e02a557edf8585", size = 13324004, upload-time = "2025-09-25T14:54:06.05Z" }, + { url = "https://files.pythonhosted.org/packages/c3/12/28fa2f597a605884deb0f65c1b1ae05111051b2a7030f5d8a4ff7f4599ba/ruff-0.13.2-py3-none-win_arm64.whl", hash = "sha256:da711b14c530412c827219312b7d7fbb4877fb31150083add7e8c5336549cea7", size = 12484437, upload-time = "2025-09-25T14:54:08.022Z" }, ] [[package]] @@ -2136,15 +2207,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.37.1" +version = "2.39.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/be/ffc232c32d0be18f8e4eff7a22dffc1f1fef2894703d64cc281a80e75da6/sentry_sdk-2.37.1.tar.gz", hash = "sha256:531751da91aa62a909b42a7be155b41f6bb0de9df6ae98441d23b95de2f98475", size = 346235, upload-time = "2025-09-09T13:48:27.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/72/43294fa4bdd75c51610b5104a3ff834459ba653abb415150aa7826a249dd/sentry_sdk-2.39.0.tar.gz", hash = "sha256:8c185854d111f47f329ab6bc35993f28f7a6b7114db64aa426b326998cfa14e9", size = 348556, upload-time = "2025-09-25T09:15:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c3/cba447ab531331d165d9003c04473be944a308ad916ca2345b5ef1969ed9/sentry_sdk-2.37.1-py2.py3-none-any.whl", hash = "sha256:baaaea6608ed3a639766a69ded06b254b106d32ad9d180bdbe58f3db9364592b", size = 368307, upload-time = "2025-09-09T13:48:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/4356cc64246ba7b2b920f7c97a85c3c52748e213e250b512ee8152eb559d/sentry_sdk-2.39.0-py2.py3-none-any.whl", hash = "sha256:ba655ca5e57b41569b18e2a5552cb3375209760a5d332cdd87c6c3f28f729602", size = 370851, upload-time = "2025-09-25T09:15:36.35Z" }, ] [[package]] @@ -2244,14 +2315,14 @@ wheels = [ [[package]] name = "types-cffi" -version = "1.17.0.20250822" +version = "1.17.0.20250915" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/0c/76a48cb6e742cac4d61a4ec632dd30635b6d302f5acdc2c0a27572ac7ae3/types_cffi-1.17.0.20250822.tar.gz", hash = "sha256:bf6f5a381ea49da7ff895fae69711271e6192c434470ce6139bf2b2e0d0fa08d", size = 17130, upload-time = "2025-08-22T03:04:02.445Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/98/ea454cea03e5f351323af6a482c65924f3c26c515efd9090dede58f2b4b6/types_cffi-1.17.0.20250915.tar.gz", hash = "sha256:4362e20368f78dabd5c56bca8004752cc890e07a71605d9e0d9e069dbaac8c06", size = 17229, upload-time = "2025-09-15T03:01:25.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/f7/68029931e7539e3246b33386a19c475f234c71d2a878411847b20bb31960/types_cffi-1.17.0.20250822-py3-none-any.whl", hash = "sha256:183dd76c1871a48936d7b931488e41f0f25a7463abe10b5816be275fc11506d5", size = 20083, upload-time = "2025-08-22T03:04:01.466Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ec/092f2b74b49ec4855cdb53050deb9699f7105b8fda6fe034c0781b8687f3/types_cffi-1.17.0.20250915-py3-none-any.whl", hash = "sha256:cef4af1116c83359c11bb4269283c50f0688e9fc1d7f0eeb390f3661546da52c", size = 20112, upload-time = "2025-09-15T03:01:24.187Z" }, ] [[package]] @@ -2274,11 +2345,11 @@ wheels = [ [[package]] name = "types-docutils" -version = "0.22.0.20250822" +version = "0.22.2.20250924" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b4/e3/b28d7786f4a5170095f59846d492c2980656c30ef4405ae94156ff63151c/types_docutils-0.22.0.20250822.tar.gz", hash = "sha256:40efebeef8467ae7648a33f3fa6f778bd94d338ca1f4a1c924b206d2f687f60a", size = 56487, upload-time = "2025-08-22T03:03:07.576Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/6d/60326ba08f44629f778937d5021a342da996682d932261d48b4043c437f7/types_docutils-0.22.2.20250924.tar.gz", hash = "sha256:a13fb412676c164edec7c2f26fe52ab7b0b7c868168dacc4298f6a8069298f3d", size = 56679, upload-time = "2025-09-24T02:53:26.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/02/4822bbddf4dae6b5dfe28d257c1e1f128c8315da8709e6d1862e055c13f2/types_docutils-0.22.0.20250822-py3-none-any.whl", hash = "sha256:890d5986045b8a532b56e7f0d4979de3afc23b4543de40910ec8c71ec5f3ba99", size = 91786, upload-time = "2025-08-22T03:03:06.522Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2b/844f3a6e972515ef0890fd8bf631890b6d74c8eacb1acbf31a72820c3b45/types_docutils-0.22.2.20250924-py3-none-any.whl", hash = "sha256:a6d52e21fa70998d34d13db6891ea35920bbb20f91459ca528a3845fd0b9ec03", size = 91873, upload-time = "2025-09-24T02:53:24.824Z" }, ] [[package]] @@ -2296,11 +2367,11 @@ wheels = [ [[package]] name = "types-greenlet" -version = "3.2.0.20250809" +version = "3.2.0.20250915" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/e8/50e2d3ad45ded82db9887979e1e9ec60be537aa144bf4f5ec9bc4f48bbf9/types_greenlet-3.2.0.20250809.tar.gz", hash = "sha256:d7261472498d9f33f37d706bc221f2d24d77b9560388cf14cef15888e2e3f249", size = 8784, upload-time = "2025-08-09T03:14:51.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d7/a76e2cb6fdbc2ecaf37a330fe3168d8d89c5f64b939da78fc62c89febab5/types_greenlet-3.2.0.20250915.tar.gz", hash = "sha256:831a390b1d4789b173b067ac546a4024fa9c43825db9f66194916dd0c85e3925", size = 8861, upload-time = "2025-09-15T03:01:18.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/9d/fd9f6ca145dc47a59346a336becc3f5afd551d6f289dfbd0d971bcccd12a/types_greenlet-3.2.0.20250809-py3-none-any.whl", hash = "sha256:97f595d561114d8e3b8ade8ac21f5428a749bcf831f5540fd0cccb0804a5df85", size = 8780, upload-time = "2025-08-09T03:14:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ae/dd/4fe5803ea70ceada70746d9e90045d673a49093907c7a6108650de59d257/types_greenlet-3.2.0.20250915-py3-none-any.whl", hash = "sha256:59c8866b6ef8b1c62e289c01c90c8544aef37c610bfa13f93099d992327ae1c7", size = 8809, upload-time = "2025-09-15T03:01:17.327Z" }, ] [[package]] @@ -2317,20 +2388,20 @@ wheels = [ [[package]] name = "types-openpyxl" -version = "3.1.5.20250822" +version = "3.1.5.20250919" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/7f/ea358482217448deafdb9232f198603511d2efa99e429822256f2b38975a/types_openpyxl-3.1.5.20250822.tar.gz", hash = "sha256:c8704a163e3798290d182c13c75da85f68cd97ff9b35f0ebfb94cf72f8b67bb3", size = 100858, upload-time = "2025-08-22T03:03:31.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/12/8bc4a25d49f1e4b7bbca868daa3ee80b1983d8137b4986867b5b65ab2ecd/types_openpyxl-3.1.5.20250919.tar.gz", hash = "sha256:232b5906773eebace1509b8994cdadda043f692cfdba9bfbb86ca921d54d32d7", size = 100880, upload-time = "2025-09-19T02:54:39.997Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/e8/cac4728e8dcbeb69d6de7de26bb9edb508e9f5c82476ecda22b58b939e60/types_openpyxl-3.1.5.20250822-py3-none-any.whl", hash = "sha256:da7a430d99c48347acf2dc351695f9db6ff90ecb761fed577b4a98fef2d0f831", size = 166093, upload-time = "2025-08-22T03:03:30.686Z" }, + { url = "https://files.pythonhosted.org/packages/36/3c/d49cf3f4489a10e9ddefde18fd258f120754c5825d06d145d9a0aaac770b/types_openpyxl-3.1.5.20250919-py3-none-any.whl", hash = "sha256:bd06f18b12fd5e1c9f0b666ee6151d8140216afa7496f7ebb9fe9d33a1a3ce99", size = 166078, upload-time = "2025-09-19T02:54:38.657Z" }, ] [[package]] name = "types-pexpect" -version = "4.9.0.20250809" +version = "4.9.0.20250916" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/a2/29564e69dee62f0f887ba7bfffa82fa4975504952e6199b218d3b403becd/types_pexpect-4.9.0.20250809.tar.gz", hash = "sha256:17a53c785b847c90d0be9149b00b0254e6e92c21cd856e853dac810ddb20101f", size = 13240, upload-time = "2025-08-09T03:15:04.554Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/e6/cc43e306dc7de14ec7861c24ac4957f688741ae39ae685049695d796b587/types_pexpect-4.9.0.20250916.tar.gz", hash = "sha256:69e5fed6199687a730a572de780a5749248a4c5df2ff1521e194563475c9928d", size = 13322, upload-time = "2025-09-16T02:49:25.61Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/1b/4d557287e6672feb749cf0d8ef5eb19189aff043e73e509e3775febc1cf1/types_pexpect-4.9.0.20250809-py3-none-any.whl", hash = "sha256:d19d206b8a7c282dac9376f26f072e036d22e9cf3e7d8eba3f477500b1f39101", size = 17039, upload-time = "2025-08-09T03:15:03.528Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6d/7740e235a9fb2570968da7d386d7feb511ce68cd23472402ff8cdf7fc78f/types_pexpect-4.9.0.20250916-py3-none-any.whl", hash = "sha256:7fa43cb96042ac58bc74f7c28e5d85782be0ee01344149886849e9d90936fe8a", size = 17057, upload-time = "2025-09-16T02:49:24.546Z" }, ] [[package]] @@ -2374,23 +2445,23 @@ wheels = [ [[package]] name = "types-regex" -version = "2025.9.1.20250903" +version = "2025.9.18.20250921" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/4b/b190c989979ebc7efc6dd1f6fbc3a2a84d4aff0c2f0805814c2c51cf20f5/types_regex-2025.9.1.20250903.tar.gz", hash = "sha256:230116f64b5c08b06109d950086e55fc809b8a2a5d13e80218ac7cf34f9aac84", size = 12455, upload-time = "2025-09-03T02:47:33.951Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/4a/493aa95abb5733f52740df4ffe52ed802d9305cd318e6b7bb386ff6d82f8/types_regex-2025.9.18.20250921.tar.gz", hash = "sha256:e1700c21b1c31290e4a6dd62584ed1f1d69d704ed1676b68a8eda2d1d2420c3f", size = 12438, upload-time = "2025-09-21T03:01:00.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/3b/9ffd05ae4e99c369acd563d23754263884dc737fd67369d0eadf449e6055/types_regex-2025.9.1.20250903-py3-none-any.whl", hash = "sha256:93ce5eb3949e27fc9f329aaed47386dba6f16695650c61e55035e2d26497b8e7", size = 10347, upload-time = "2025-09-03T02:47:32.911Z" }, + { url = "https://files.pythonhosted.org/packages/52/1f/5b26a2ef84b0a98a43eae46fb1c4bbb2928ead4397d22153685e6304d090/types_regex-2025.9.18.20250921-py3-none-any.whl", hash = "sha256:7bd96178829f4499be9109ce7e8bf7bd8e00d06f6e51c5a33213599eb94e3ec1", size = 10358, upload-time = "2025-09-21T03:00:59.293Z" }, ] [[package]] name = "types-requests" -version = "2.32.4.20250809" +version = "2.32.4.20250913" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, ] [[package]] @@ -2499,28 +2570,28 @@ wheels = [ [[package]] name = "uv" -version = "0.8.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/4c/c270c6b8ed3e8c7fe38ea0b99df9eff09c332421b93d55a158371f75220e/uv-0.8.17.tar.gz", hash = "sha256:2afd4525a53c8ab3a11a5a15093c503d27da67e76257a649b05e4f0bc2ebb5ae", size = 3615060, upload-time = "2025-09-10T21:51:25.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/7d/bbaa45c88b2c91e02714a8a5c9e787c47e4898bddfdd268569163492ba45/uv-0.8.17-py3-none-linux_armv6l.whl", hash = "sha256:c51c9633ca93ef63c07df2443941e6264efd2819cc9faabfd9fe11899c6a0d6a", size = 20242144, upload-time = "2025-09-10T21:50:18.081Z" }, - { url = "https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c28fba6d7bb5c34ade2c8da5000faebe8425a287f42a043ca01ceb24ebc81590", size = 19363081, upload-time = "2025-09-10T21:50:22.731Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b009f1ec9e28de00f76814ad66e35aaae82c98a0f24015de51943dcd1c2a1895", size = 17943513, upload-time = "2025-09-10T21:50:25.824Z" }, - { url = "https://files.pythonhosted.org/packages/63/1c/14fd54c852fd592a2b5da4b7960f3bf4a15c7e51eb20eaddabe8c8cca32d/uv-0.8.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:84d56ae50ca71aec032577adf9737974554a82a94e52cee57722745656c1d383", size = 19507222, upload-time = "2025-09-10T21:50:29.237Z" }, - { url = "https://files.pythonhosted.org/packages/be/47/f6a68cc310feca37c965bcbd57eb999e023d35eaeda9c9759867bf3ed232/uv-0.8.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:85c2140f8553b9a4387a7395dc30cd151ef94046785fe8b198f13f2c380fb39b", size = 19865652, upload-time = "2025-09-10T21:50:32.87Z" }, - { url = "https://files.pythonhosted.org/packages/ab/6a/fdeb2d4a2635a6927c6d549b07177bcaf6ce15bdef58e8253e75c1b70f54/uv-0.8.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2076119783e4a6d3c9e25638956cb123f0eabf4d7d407d9661cdf7f84818dcb9", size = 20831760, upload-time = "2025-09-10T21:50:37.803Z" }, - { url = "https://files.pythonhosted.org/packages/d0/4c/bd58b8a76015aa9ac49d6b4e1211ae1ca98a0aade0c49e1a5f645fb5cd38/uv-0.8.17-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:707a55660d302924fdbcb509e63dfec8842e19d35b69bcc17af76c25db15ad6f", size = 22209056, upload-time = "2025-09-10T21:50:41.749Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2e/28f59c00a2ed6532502fb1e27da9394e505fb7b41cc0274475104b43561b/uv-0.8.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1824b76911a14aaa9eee65ad9e180e6a4d2d7c86826232c2f28ae86aee56ed0e", size = 21871684, upload-time = "2025-09-10T21:50:45.331Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1d/a8a4fc08de1f767316467e7a1989bb125734b7ed9cd98ce8969386a70653/uv-0.8.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb9b515cc813fb1b08f1e7592f76e437e2fb44945e53cde4fee11dee3b16d0c3", size = 21145154, upload-time = "2025-09-10T21:50:50.388Z" }, - { url = "https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d30d02fb65193309fc12a20f9e1a9fab67f469d3e487a254ca1145fd06788f", size = 21106619, upload-time = "2025-09-10T21:50:54.5Z" }, - { url = "https://files.pythonhosted.org/packages/6e/93/c310f0153b9dfe79bdd7f7eaef6380a8545c8939dbfc4e6bdee8f3ee7050/uv-0.8.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3941cecd9a6a46d3d4505753912c9cf3e8ae5eea30b9d0813f3656210f8c5d01", size = 19777591, upload-time = "2025-09-10T21:50:57.765Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/971d3c84c2f09cf8df4536c33644e6b97e10a259d8630a0c1696c1fa6e94/uv-0.8.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:cd0ad366cfe4cbe9212bd660b5b9f3a827ff35a7601cefdac2d153bfc8079eb7", size = 20845039, upload-time = "2025-09-10T21:51:01.167Z" }, - { url = "https://files.pythonhosted.org/packages/4a/29/8ad9038e75cb91f54b81cc933dd14fcfa92fa6f8706117d43d4251a8a662/uv-0.8.17-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:505854bc75c497b95d2c65590291dc820999a4a7d9dfab4f44a9434a6cff7b5f", size = 19820370, upload-time = "2025-09-10T21:51:04.616Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c9/fc8482d1e7dfe187c6e03dcefbac0db41a5dd72aa7b017c0f80f91a04444/uv-0.8.17-py3-none-musllinux_1_1_i686.whl", hash = "sha256:dc479f661da449df37d68b36fdffa641e89fb53ad38c16a5c9f98f3211785b63", size = 20289951, upload-time = "2025-09-10T21:51:08.605Z" }, - { url = "https://files.pythonhosted.org/packages/2d/84/ad878ed045f02aa973be46636c802d494f8270caf5ea8bd04b7bbc68aa23/uv-0.8.17-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a1d11cd805be6d137ffef4a8227905f87f459031c645ac5031c30a3bcd08abd6", size = 21234644, upload-time = "2025-09-10T21:51:12.429Z" }, - { url = "https://files.pythonhosted.org/packages/f8/03/3fa2641513922988e641050b3adbc87de527f44c2cc8328510703616be6a/uv-0.8.17-py3-none-win32.whl", hash = "sha256:d13a616eb0b2b33c7aa09746cc85860101d595655b58653f0b499af19f33467c", size = 19216757, upload-time = "2025-09-10T21:51:16.021Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl", hash = "sha256:cf85b84b81b41d57a9b6eeded8473ec06ace8ee959ad0bb57e102b5ad023bd34", size = 21125811, upload-time = "2025-09-10T21:51:19.397Z" }, - { url = "https://files.pythonhosted.org/packages/50/a2/29f57b118b3492c9d5ab1a99ba4906e7d7f8b658881d31bc2c4408d64d07/uv-0.8.17-py3-none-win_arm64.whl", hash = "sha256:64d649a8c4c3732b05dc712544963b004cf733d95fdc5d26f43c5493553ff0a7", size = 19564631, upload-time = "2025-09-10T21:51:22.599Z" }, +version = "0.8.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/39/231e123458d50dd497cf6d27b592f5d3bc3e2e50f496b56859865a7b22e3/uv-0.8.22.tar.gz", hash = "sha256:e6e1289c411d43e0ca245f46e76457f3807de646d90b656591b6cf46348bed5c", size = 3667007, upload-time = "2025-09-23T20:35:14.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e6/bb440171dd8a36d0f9874b4c71778f7bbc83e62ccf42c62bd1583c802793/uv-0.8.22-py3-none-linux_armv6l.whl", hash = "sha256:7350c5f82d9c38944e6466933edcf96a90e0cb85eae5c0e53a5bc716d6f62332", size = 20554993, upload-time = "2025-09-23T20:34:26.549Z" }, + { url = "https://files.pythonhosted.org/packages/28/e9/813f7eb9fb9694c4024362782c8933e37887b5195e189f80dc40f2da5958/uv-0.8.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89944e99b04cc8542cb5931306f1c593f00c9d6f2b652fffc4d84d12b915f911", size = 19565276, upload-time = "2025-09-23T20:34:30.436Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/bf37d86af6e16e45fa2b1a03300784ff3297aa9252a23dfbeaf6e391e72e/uv-0.8.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6706b782ad75662df794e186d16b9ffa4946d57c88f21d0eadfd43425794d1b0", size = 18162303, upload-time = "2025-09-23T20:34:32.761Z" }, + { url = "https://files.pythonhosted.org/packages/e4/eb/289b6a59fff1613958499a886283f52403c5ce4f0a8a550b86fbd70e8e4f/uv-0.8.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d6a33bd5309f8fb77d9fc249bb17f77a23426e6153e43b03ca1cd6640f0a423d", size = 19982769, upload-time = "2025-09-23T20:34:34.962Z" }, + { url = "https://files.pythonhosted.org/packages/df/ba/2fcc3ce75be62eecf280f3cbe74d186f371a468fad3167b5a34dee2f904e/uv-0.8.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a982bdd5d239dd6dd2b4219165e209c75af1e1819730454ee46d65b3ccf77a3", size = 20163849, upload-time = "2025-09-23T20:34:37.744Z" }, + { url = "https://files.pythonhosted.org/packages/f4/4d/4fc9a508c2c497a80c41710c96f1782a29edecffcac742f3843af061ba8f/uv-0.8.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58b6fb191a04b922dc3c8fea6660f58545a651843d7d0efa9ae69164fca9e05d", size = 21130147, upload-time = "2025-09-23T20:34:40.414Z" }, + { url = "https://files.pythonhosted.org/packages/71/79/6bcb3c3c3b7c9cb1a162a76dca2b166752e4ba39ec90e802b252f0a54039/uv-0.8.22-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8ea724ae9f15c0cb4964e9e2e1b21df65c56ae02a54dc1d8a6ea44a52d819268", size = 22561974, upload-time = "2025-09-23T20:34:42.843Z" }, + { url = "https://files.pythonhosted.org/packages/3f/98/89bb29d82ff7e5ab1b5e862d9bdc12b1d3a4d5201cf558432487e29cc448/uv-0.8.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7378127cbd6ebce8ba6d9bdb88aa8ea995b579824abb5ec381c63b3a123a43be", size = 22183189, upload-time = "2025-09-23T20:34:45.57Z" }, + { url = "https://files.pythonhosted.org/packages/95/b0/354c7d7d11fff2ee97bb208f0fec6b09ae885c0d591b6eff2d7b84cc6695/uv-0.8.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e761ca7df8a0059b3fae6bc2c1db24583fa00b016e35bd22a5599d7084471a7", size = 21492888, upload-time = "2025-09-23T20:34:48.45Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a9/a83cee9b8cf63e57ce64ba27c77777cc66410e144fd178368f55af1fa18d/uv-0.8.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efec4ef5acddc35f0867998c44e0b15fc4dace1e4c26d01443871a2fbb04bf6", size = 21252972, upload-time = "2025-09-23T20:34:50.862Z" }, + { url = "https://files.pythonhosted.org/packages/0f/0c/71d5d5d3fca7aa788d63297a06ca26d3585270342277b52312bb693b100c/uv-0.8.22-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9eb3b4abfa25e07d7e1bb4c9bb8dbbdd51878356a37c3c4a2ece3d68d4286f28", size = 20115520, upload-time = "2025-09-23T20:34:53.165Z" }, + { url = "https://files.pythonhosted.org/packages/da/90/57fae2798be1e71692872b8304e2e2c345eacbe2070bdcbba6d5a7675fa1/uv-0.8.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b1fdffc2e71892ce648b66317e478fe8884d0007e20cfa582fff3dcea588a450", size = 21168787, upload-time = "2025-09-23T20:34:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f6/23c8d8fdd1084603795f6344eee8e763ba06f891e863397fe5b7b532cb58/uv-0.8.22-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:f6ded9bacb31441d788afca397b8b884ebc2e70f903bea0a38806194be4b249c", size = 20170112, upload-time = "2025-09-23T20:34:58.008Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/801d517964a7200014897522ae067bf7111fc2e138b38d13d9df9544bf06/uv-0.8.22-py3-none-musllinux_1_1_i686.whl", hash = "sha256:aefa0cb27a86d2145ca9290a1e99c16a17ea26a4f14a89fb7336bc19388427cc", size = 20537608, upload-time = "2025-09-23T20:35:00.44Z" }, + { url = "https://files.pythonhosted.org/packages/20/8a/1bd4159089f8df0128e4ceb7f4c31c23a451984a5b49c13489c70e721335/uv-0.8.22-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:9757f0b0c7d296f1e354db442ed0ce39721c06d11635ce4ee6638c5e809a9cb4", size = 21471224, upload-time = "2025-09-23T20:35:03.718Z" }, + { url = "https://files.pythonhosted.org/packages/86/ba/262d16059e3b0837728e8aa3590fc2c7bc23e0cefec81d6903b4b6af080a/uv-0.8.22-py3-none-win32.whl", hash = "sha256:36c7aecdb0044caf15ace00da00af172759c49c832f0017b7433d80f46552cd3", size = 19350586, upload-time = "2025-09-23T20:35:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/38/82/94f08992eeb193dc3d5baac437d1867cd37f040f34c7b1a4b1bde2bc4b4b/uv-0.8.22-py3-none-win_amd64.whl", hash = "sha256:cda349c9ea53644d8d9ceae30db71616b733eb5330375ab4259765aef494b74e", size = 21355960, upload-time = "2025-09-23T20:35:09.472Z" }, + { url = "https://files.pythonhosted.org/packages/f9/00/2c7a93bbe93b74dc0496a8e875bac11027cb30c29636c106c6e49038b95f/uv-0.8.22-py3-none-win_arm64.whl", hash = "sha256:2a436b941b6e79fe1e1065b705a5689d72210f4367cbe885e19910cbcde2e4a1", size = 19778983, upload-time = "2025-09-23T20:35:12.188Z" }, ] [[package]] @@ -2540,11 +2611,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] [[package]] From 2022e85094666603af8137d4299399d367e979c4 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Sun, 28 Sep 2025 15:09:59 -0700 Subject: [PATCH 32/34] Update cli.py --- codeflash/cli_cmds/cli.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py index b3449fd2f..933274c19 100644 --- a/codeflash/cli_cmds/cli.py +++ b/codeflash/cli_cmds/cli.py @@ -1,3 +1,4 @@ +import importlib.util import logging import sys from argparse import SUPPRESS, ArgumentParser, Namespace @@ -145,6 +146,14 @@ def process_and_validate_cmd_args(args: Namespace) -> Namespace: if env_utils.is_ci(): args.no_pr = True + if getattr(args, "async", False) and importlib.util.find_spec("pytest_asyncio") is None: + logger.warning( + "Warning: The --async flag requires pytest-asyncio to be installed.\n" + "Please install it using:\n" + ' pip install "codeflash[asyncio]"' + ) + raise SystemExit(1) + return args From b992f71b6e7e8e5da9143d4581dd1b9b6d87f51c Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Sep 2025 13:26:18 -0700 Subject: [PATCH 33/34] loosen gain % for E2E --- .github/workflows/e2e-bubblesort-pytest-nogit.yaml | 2 +- tests/scripts/end_to_end_test_benchmark_sort.py | 2 +- tests/scripts/end_to_end_test_bubblesort_pytest.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-bubblesort-pytest-nogit.yaml b/.github/workflows/e2e-bubblesort-pytest-nogit.yaml index 9ab5e7687..f30e4f14e 100644 --- a/.github/workflows/e2e-bubblesort-pytest-nogit.yaml +++ b/.github/workflows/e2e-bubblesort-pytest-nogit.yaml @@ -20,7 +20,7 @@ jobs: COLUMNS: 110 MAX_RETRIES: 3 RETRY_DELAY: 5 - EXPECTED_IMPROVEMENT_PCT: 300 + EXPECTED_IMPROVEMENT_PCT: 70 CODEFLASH_END_TO_END: 1 steps: - name: 🛎️ Checkout diff --git a/tests/scripts/end_to_end_test_benchmark_sort.py b/tests/scripts/end_to_end_test_benchmark_sort.py index 64aabe384..643ab2eb0 100644 --- a/tests/scripts/end_to_end_test_benchmark_sort.py +++ b/tests/scripts/end_to_end_test_benchmark_sort.py @@ -11,7 +11,7 @@ def run_test(expected_improvement_pct: int) -> bool: function_name="sorter", benchmarks_root=cwd / "tests" / "pytest" / "benchmarks", test_framework="pytest", - min_improvement_x=1.0, + min_improvement_x=0.70, coverage_expectations=[ CoverageExpectation( function_name="sorter", expected_coverage=100.0, expected_lines=[2, 3, 4, 5, 6, 7, 8, 9, 10] diff --git a/tests/scripts/end_to_end_test_bubblesort_pytest.py b/tests/scripts/end_to_end_test_bubblesort_pytest.py index d714703aa..e5dd7c689 100644 --- a/tests/scripts/end_to_end_test_bubblesort_pytest.py +++ b/tests/scripts/end_to_end_test_bubblesort_pytest.py @@ -9,7 +9,7 @@ def run_test(expected_improvement_pct: int) -> bool: file_path="bubble_sort.py", function_name="sorter", test_framework="pytest", - min_improvement_x=1.0, + min_improvement_x=0.70, coverage_expectations=[ CoverageExpectation( function_name="sorter", expected_coverage=100.0, expected_lines=[2, 3, 4, 5, 6, 7, 8, 9, 10] From 782dba1f234c5e524db8876127946c3110eb6cbe Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Sep 2025 13:43:00 -0700 Subject: [PATCH 34/34] update this one too --- .github/workflows/e2e-bubblesort-unittest.yaml | 2 +- tests/scripts/end_to_end_test_bubblesort_unittest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-bubblesort-unittest.yaml b/.github/workflows/e2e-bubblesort-unittest.yaml index 0401f9876..c4177e706 100644 --- a/.github/workflows/e2e-bubblesort-unittest.yaml +++ b/.github/workflows/e2e-bubblesort-unittest.yaml @@ -20,7 +20,7 @@ jobs: COLUMNS: 110 MAX_RETRIES: 3 RETRY_DELAY: 5 - EXPECTED_IMPROVEMENT_PCT: 300 + EXPECTED_IMPROVEMENT_PCT: 40 CODEFLASH_END_TO_END: 1 steps: - name: 🛎️ Checkout diff --git a/tests/scripts/end_to_end_test_bubblesort_unittest.py b/tests/scripts/end_to_end_test_bubblesort_unittest.py index c811e6a31..074ba662f 100644 --- a/tests/scripts/end_to_end_test_bubblesort_unittest.py +++ b/tests/scripts/end_to_end_test_bubblesort_unittest.py @@ -6,7 +6,7 @@ def run_test(expected_improvement_pct: int) -> bool: config = TestConfig( - file_path="bubble_sort.py", function_name="sorter", test_framework="unittest", min_improvement_x=3.0 + file_path="bubble_sort.py", function_name="sorter", test_framework="unittest", min_improvement_x=0.40 ) cwd = (pathlib.Path(__file__).parent.parent.parent / "code_to_optimize").resolve() return run_codeflash_command(cwd, config, expected_improvement_pct)