diff --git a/pyproject.toml b/pyproject.toml index 6a4886c1b..d2ca2b2ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,9 +45,8 @@ requires-python = ">=3.10" "vulture==2.14", "pytest~=9.0", "pytest-cov~=7.0", - "pytest-asyncio~=1.3", - "import-linter~=2.10", - "pytest-deadfixtures~=3.1", + "import-linter~=2.5", + "pytest-deadfixtures~=2.2", "taplo~=0.9.3", ] docs = [ diff --git a/src/cloudai/_core/base_runner.py b/src/cloudai/_core/base_runner.py index a08814f13..10f6bc01c 100644 --- a/src/cloudai/_core/base_runner.py +++ b/src/cloudai/_core/base_runner.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,10 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio import logging +import time from abc import ABC, abstractmethod -from asyncio import Task from pathlib import Path from typing import Dict, List @@ -71,33 +70,33 @@ def __init__(self, mode: str, system: System, test_scenario: TestScenario, outpu logging.debug(f"{self.__class__.__name__} initialized") self.shutting_down = False - async def shutdown(self): + def shutdown(self): """Gracefully shut down the runner, terminating all outstanding jobs.""" self.shutting_down = True logging.info("Terminating all jobs...") for job in self.jobs: logging.info(f"Terminating job {job.id} for test {job.test_run.name}") self.system.kill(job) - logging.info("All jobs have been killed.") + logging.info("Waiting for all jobs to be killed.") - async def run(self): - """Asynchronously run the test scenario.""" + def run(self): + """Run the test scenario.""" if self.shutting_down: return total_tests = len(self.test_scenario.test_runs) dependency_free_trs = self.find_dependency_free_tests() for tr in dependency_free_trs: - await self.submit_test(tr) + self.submit_test(tr) logging.debug(f"Total tests: {total_tests}, dependency free tests: {[tr.name for tr in dependency_free_trs]}") while self.jobs: - await self.check_start_post_init_dependencies() - await self.monitor_jobs() + self.check_start_post_init_dependencies() + self.monitor_jobs() logging.debug(f"sleeping for {self.monitor_interval} seconds") - await asyncio.sleep(self.monitor_interval) + time.sleep(self.monitor_interval) - async def submit_test(self, tr: TestRun): + def submit_test(self, tr: TestRun): """ Start a dependency-free test. @@ -118,7 +117,7 @@ async def submit_test(self, tr: TestRun): def on_job_submit(self, tr: TestRun) -> None: return - async def delayed_submit_test(self, tr: TestRun, delay: int = 5): + def delayed_submit_test(self, tr: TestRun, delay: int = 5): """ Delay the start of a test based on start_post_comp dependency. @@ -127,8 +126,8 @@ async def delayed_submit_test(self, tr: TestRun, delay: int = 5): delay (int): Delay in seconds before starting the test. """ logging.debug(f"Delayed start for test {tr.name} by {delay} seconds.") - await asyncio.sleep(delay) - await self.submit_test(tr) + time.sleep(delay) + self.submit_test(tr) @abstractmethod def _submit_test(self, tr: TestRun) -> BaseJob: @@ -143,7 +142,7 @@ def _submit_test(self, tr: TestRun) -> BaseJob: """ pass - async def check_start_post_init_dependencies(self): + def check_start_post_init_dependencies(self): """ Check and handle start_post_init dependencies. @@ -164,9 +163,9 @@ async def check_start_post_init_dependencies(self): logging.debug(f"start_post_init for test {tr.name} ({is_running=}, {is_completed=}, {self.mode=})") if is_running or is_completed: - await self.check_and_schedule_start_post_init_dependent_tests(tr) + self.check_and_schedule_start_post_init_dependent_tests(tr) - async def check_and_schedule_start_post_init_dependent_tests(self, started_test_run: TestRun): + def check_and_schedule_start_post_init_dependent_tests(self, started_test_run: TestRun): """ Schedule tests with a start_post_init dependency on the provided started_test. @@ -177,7 +176,7 @@ async def check_and_schedule_start_post_init_dependent_tests(self, started_test_ if tr not in self.testrun_to_job_map: for dep_type, dep in tr.dependencies.items(): if (dep_type == "start_post_init") and (dep.test_run == started_test_run): - await self.delayed_submit_test(tr) + self.delayed_submit_test(tr) def find_dependency_free_tests(self) -> List[TestRun]: """ @@ -229,7 +228,7 @@ def get_job_output_path(self, tr: TestRun) -> Path: return job_output_path - async def monitor_jobs(self) -> int: + def monitor_jobs(self) -> int: """ Monitor the status of jobs, handle end_post_comp dependencies, and schedule start_post_comp dependent jobs. @@ -248,20 +247,20 @@ async def monitor_jobs(self) -> int: if self.mode == "dry-run": successful_jobs_count += 1 - await self.handle_job_completion(job) + self.handle_job_completion(job) else: if self.test_scenario.job_status_check: job_status_result = self.get_job_status(job) if job_status_result.is_successful: successful_jobs_count += 1 - await self.handle_job_completion(job) + self.handle_job_completion(job) else: error_message = ( f"Job {job.id} for test {job.test_run.name} failed: {job_status_result.error_message}" ) logging.error(error_message) - await self.handle_job_completion(job) - await self.shutdown() + self.handle_job_completion(job) + self.shutdown() raise JobFailureError(job.test_run.name, error_message, job_status_result.error_message) else: job_status_result = self.get_job_status(job) @@ -271,7 +270,7 @@ async def monitor_jobs(self) -> int: ) logging.error(error_message) successful_jobs_count += 1 - await self.handle_job_completion(job) + self.handle_job_completion(job) return successful_jobs_count @@ -296,7 +295,7 @@ def get_job_status(self, job: BaseJob) -> JobStatusResult: return workload_run_results return JobStatusResult(is_successful=True) - async def handle_job_completion(self, completed_job: BaseJob): + def handle_job_completion(self, completed_job: BaseJob): """ Handle the completion of a job, including dependency management and iteration control. @@ -316,9 +315,9 @@ async def handle_job_completion(self, completed_job: BaseJob): completed_job.test_run.current_iteration += 1 msg = f"Re-running job for iteration {completed_job.test_run.current_iteration}" logging.info(msg) - await self.submit_test(completed_job.test_run) + self.submit_test(completed_job.test_run) else: - await self.handle_dependencies(completed_job) + self.handle_dependencies(completed_job) def on_job_completion(self, job: BaseJob) -> None: """ @@ -332,37 +331,27 @@ def on_job_completion(self, job: BaseJob) -> None: """ return - async def handle_dependencies(self, completed_job: BaseJob) -> List[Task]: + def handle_dependencies(self, completed_job: BaseJob): """ Handle the start_post_comp and end_post_comp dependencies for a completed job. Args: completed_job (BaseJob): The job that has just been completed. - - Returns: - List[asyncio.Task]: A list of asyncio.Task objects created for handling the dependencies. """ - tasks = [] - # Handling start_post_comp dependencies for tr in self.test_scenario.test_runs: if tr not in self.testrun_to_job_map: for dep_type, dep in tr.dependencies.items(): if dep_type == "start_post_comp" and dep.test_run == completed_job.test_run: - task = await self.delayed_submit_test(tr) - if task: - tasks.append(task) + self.delayed_submit_test(tr) # Handling end_post_comp dependencies for test, dependent_job in self.testrun_to_job_map.items(): for dep_type, dep in test.dependencies.items(): if dep_type == "end_post_comp" and dep.test_run == completed_job.test_run: - task = await self.delayed_kill_job(dependent_job) - tasks.append(task) - - return tasks + self.delayed_kill_job(dependent_job) - async def delayed_kill_job(self, job: BaseJob, delay: int = 0): + def delayed_kill_job(self, job: BaseJob, delay: int = 0): """ Schedule termination of a Standalone job after a specified delay. @@ -371,7 +360,7 @@ async def delayed_kill_job(self, job: BaseJob, delay: int = 0): delay (int): Delay in seconds after which the job should be terminated. """ logging.info(f"Scheduling termination of job {job.id} after {delay} seconds.") - await asyncio.sleep(delay) + time.sleep(delay) job.terminated_by_dependency = True self.system.kill(job) diff --git a/src/cloudai/_core/runner.py b/src/cloudai/_core/runner.py index 79c9bf605..b6647a895 100644 --- a/src/cloudai/_core/runner.py +++ b/src/cloudai/_core/runner.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio import datetime import logging from types import FrameType @@ -80,39 +79,18 @@ def create_runner(self, mode: str, system: System, test_scenario: TestScenario) return runner_class(mode, system, test_scenario, results_root) - async def run(self): + def run(self): """Run the test scenario using the instantiated runner.""" try: - await self.runner.run() + self.runner.run() logging.debug("All jobs finished successfully.") - except asyncio.CancelledError: - logging.info("Runner cancelled, performing cleanup...") - await self.runner.shutdown() - return except JobFailureError as exc: logging.debug(f"Runner failed JobFailure exception: {exc}", exc_info=True) - def _cancel_all(self): - # the below code might look excessive, this is to address https://docs.astral.sh/ruff/rules/asyncio-dangling-task/ - shutdown_task = asyncio.create_task(self.runner.shutdown()) - tasks = {shutdown_task} - shutdown_task.add_done_callback(tasks.discard) - - for task in asyncio.all_tasks(): - if task == shutdown_task: - continue - - logging.debug(f"Cancelling task: {task}") - try: - task.cancel() - except asyncio.CancelledError as exc: - logging.debug(f"Error cancelling task: {task}, {exc}", exc_info=True) - pass - def cancel_on_signal( self, signum: int, frame: Optional[FrameType], # noqa: Vulture ): logging.info(f"Signal {signum} received, shutting down...") - asyncio.get_running_loop().call_soon_threadsafe(self._cancel_all) + self.runner.shutdown() diff --git a/src/cloudai/cli/handlers.py b/src/cloudai/cli/handlers.py index d474ff421..c5531698f 100644 --- a/src/cloudai/cli/handlers.py +++ b/src/cloudai/cli/handlers.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,6 @@ # limitations under the License. import argparse -import asyncio import copy import logging import signal @@ -192,7 +191,7 @@ def generate_reports(system: System, test_scenario: TestScenario, result_dir: Pa def handle_non_dse_job(runner: Runner, args: argparse.Namespace) -> None: - asyncio.run(runner.run()) + runner.run() generate_reports(runner.runner.system, runner.runner.test_scenario, runner.runner.scenario_root) logging.info("All jobs are complete.") diff --git a/src/cloudai/configurator/cloudai_gym.py b/src/cloudai/configurator/cloudai_gym.py index 1be1c0f76..b5495b891 100644 --- a/src/cloudai/configurator/cloudai_gym.py +++ b/src/cloudai/configurator/cloudai_gym.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio import copy import csv import logging @@ -113,7 +112,7 @@ def step(self, action: Any) -> Tuple[list, float, bool, dict]: self.runner.testrun_to_job_map.clear() try: - asyncio.run(self.runner.run()) + self.runner.run() except Exception as e: logging.error(f"Error running step {self.test_run.step}: {e}") diff --git a/src/cloudai/systems/runai/runai_rest_client.py b/src/cloudai/systems/runai/runai_rest_client.py index 7188d3b52..8186dec91 100644 --- a/src/cloudai/systems/runai/runai_rest_client.py +++ b/src/cloudai/systems/runai/runai_rest_client.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ from typing import Any, Dict, Optional import requests -import websockets +from websockets.sync.client import connect as ws_connect class RunAIRestClient: @@ -496,7 +496,7 @@ def is_cluster_api_available(self, cluster_domain: str) -> bool: response = requests.get(url, headers=headers) return "OK" in response.text - async def fetch_training_logs( + def fetch_training_logs( self, cluster_domain: str, project_name: str, training_task_name: str, output_file_path: Path ): if not self.is_cluster_api_available(cluster_domain): @@ -512,9 +512,11 @@ async def fetch_training_logs( } ssl_context = ssl._create_unverified_context() - async with websockets.connect(url, extra_headers=headers, ssl=ssl_context) as websocket: - with output_file_path.open("w") as log_file: - async for message in websocket: - if isinstance(message, bytes): - message = message.decode("utf-8") - log_file.write(str(message)) + with ( + ws_connect(url, additional_headers=headers, ssl=ssl_context) as websocket, + output_file_path.open("w") as log_file, + ): + for message in websocket: + if isinstance(message, bytes): + message = message.decode("utf-8") + log_file.write(str(message)) diff --git a/src/cloudai/systems/runai/runai_runner.py b/src/cloudai/systems/runai/runai_runner.py index 7263a1064..d8f0701e2 100644 --- a/src/cloudai/systems/runai/runai_runner.py +++ b/src/cloudai/systems/runai/runai_runner.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,12 +41,12 @@ def _submit_test(self, tr: TestRun) -> RunAIJob: else: raise RuntimeError("Invalid mode for submitting a test.") - async def job_completion_callback(self, job: BaseJob) -> None: + def on_job_completion(self, job: BaseJob) -> None: runai_system = cast(RunAISystem, self.system) - job = cast(RunAIJob, job) - workload_id = str(job.id) - runai_system.get_workload_events(workload_id, job.test_run.output_path / "events.txt") - await runai_system.store_logs(workload_id, job.test_run.output_path / "stdout.txt") + runai_job = cast(RunAIJob, job) + workload_id = str(runai_job.id) + runai_system.get_workload_events(workload_id, runai_job.test_run.output_path / "events.txt") + runai_system.store_logs(workload_id, runai_job.test_run.output_path / "stdout.txt") def kill_job(self, job: BaseJob) -> None: runai_system = cast(RunAISystem, self.system) diff --git a/src/cloudai/systems/runai/runai_system.py b/src/cloudai/systems/runai/runai_system.py index 05e5f0135..2cd80f050 100644 --- a/src/cloudai/systems/runai/runai_system.py +++ b/src/cloudai/systems/runai/runai_system.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -174,7 +174,7 @@ def resume_training(self, workload_id: str) -> None: self.api_client.resume_training(workload_id) # ============================ Logs ============================ - async def store_logs(self, workload_id: str, output_file_path: Path): + def store_logs(self, workload_id: str, output_file_path: Path): """Store logs for a given workload.""" training_data = self.api_client.get_training(workload_id) training = RunAITraining(**training_data) @@ -202,4 +202,4 @@ async def store_logs(self, workload_id: str, output_file_path: Path): logging.error(f"Domain for cluster {cluster_id} not found.") return - await self.api_client.fetch_training_logs(cluster_domain, project.name, training.name, output_file_path) + self.api_client.fetch_training_logs(cluster_domain, project.name, training.name, output_file_path) diff --git a/src/cloudai/systems/slurm/single_sbatch_runner.py b/src/cloudai/systems/slurm/single_sbatch_runner.py index 51b922fe0..7bb563e26 100644 --- a/src/cloudai/systems/slurm/single_sbatch_runner.py +++ b/src/cloudai/systems/slurm/single_sbatch_runner.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,9 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio import copy import logging +import time from datetime import timedelta from pathlib import Path from typing import Generator, Optional, cast @@ -180,7 +180,7 @@ def all_trs(self) -> Generator[TestRun, None, None]: tr.output_path = self.get_job_output_path(tr) yield tr - async def run(self): + def run(self): if self.shutting_down: return @@ -193,7 +193,7 @@ async def run(self): if self.shutting_down: break is_completed = True if self.mode == "dry-run" else self.system.is_job_completed(job) - await asyncio.sleep(self.system.monitor_interval) + time.sleep(self.system.monitor_interval) self.handle_dse() diff --git a/tests/test_acceptance.py b/tests/test_acceptance.py index df3bbc06b..a07a87365 100644 --- a/tests/test_acceptance.py +++ b/tests/test_acceptance.py @@ -108,7 +108,7 @@ def do_dry_run(self, tmp_path_factory: pytest.TempPathFactory, request: pytest.F log_file="debug.log", ) with ( - patch("asyncio.sleep", return_value=None), + patch("time.sleep", return_value=None), patch("cloudai.systems.slurm.SlurmSystem.is_job_completed", return_value=True), patch("cloudai.systems.slurm.SlurmSystem.is_job_running", return_value=True), patch("cloudai.util.command_shell.CommandShell.execute") as mock_execute, diff --git a/tests/test_base_runner.py b/tests/test_base_runner.py index 07fbb8d98..6598dc87c 100644 --- a/tests/test_base_runner.py +++ b/tests/test_base_runner.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio from copy import deepcopy from pathlib import Path from typing import cast @@ -50,12 +49,11 @@ def _submit_test(self, tr: TestRun) -> BaseJob: self.submitted_trs.append(tr) return BaseJob(tr, 0) - async def delayed_submit_test(self, tr: TestRun, delay: int = 0): - await super().delayed_submit_test(tr, 0) + def delayed_submit_test(self, tr: TestRun, delay: int = 0): + super().delayed_submit_test(tr, 0) - async def delayed_kill_job(self, job: BaseJob, delay: int = 0): + def delayed_kill_job(self, job: BaseJob, delay: int = 0): self.killed_by_dependency.append(job) - await asyncio.sleep(0) class MyWorkload(TestDefinition): @@ -127,40 +125,37 @@ class TestHandleDependencies: def tr_main(self, runner: MyRunner) -> TestRun: return runner.test_scenario.test_runs[0] - @pytest.mark.asyncio - async def test_no_dependencies(self, runner: MyRunner, tr_main: TestRun): - await runner.handle_dependencies(BaseJob(tr_main, 0)) + def test_no_dependencies(self, runner: MyRunner, tr_main: TestRun): + runner.handle_dependencies(BaseJob(tr_main, 0)) assert len(runner.submitted_trs) == 0 - @pytest.mark.asyncio - async def test_start_post_comp(self, runner: MyRunner, tr_main: TestRun): + def test_start_post_comp(self, runner: MyRunner, tr_main: TestRun): tr_dep = deepcopy(tr_main) tr_dep.dependencies = {"start_post_comp": TestDependency(tr_main)} runner.test_scenario.test_runs.append(tr_dep) - await runner.handle_dependencies(BaseJob(tr_dep, 0)) # self, should not trigger anything + runner.handle_dependencies(BaseJob(tr_dep, 0)) # self, should not trigger anything assert len(runner.submitted_trs) == 0 - await runner.handle_dependencies(BaseJob(tr_main, 0)) + runner.handle_dependencies(BaseJob(tr_main, 0)) assert len(runner.submitted_trs) == 1 assert runner.submitted_trs[0] == tr_dep - @pytest.mark.asyncio - async def test_end_post_comp(self, runner: MyRunner, tr_main: TestRun): + def test_end_post_comp(self, runner: MyRunner, tr_main: TestRun): tr_dep = deepcopy(tr_main) tr_dep.dependencies = {"end_post_comp": TestDependency(tr_main)} runner.test_scenario.test_runs.append(tr_dep) # self not running, main completed -> nothing to kill - await runner.handle_dependencies(BaseJob(tr_main, 0)) + runner.handle_dependencies(BaseJob(tr_main, 0)) assert len(runner.killed_by_dependency) == 0 # self is running, main completed -> should kill - await runner.submit_test(tr_dep) + runner.submit_test(tr_dep) - await runner.handle_dependencies(BaseJob(tr_dep, 0)) # self, should not kill + runner.handle_dependencies(BaseJob(tr_dep, 0)) # self, should not kill assert len(runner.killed_by_dependency) == 0 - await runner.handle_dependencies(BaseJob(tr_main, 0)) + runner.handle_dependencies(BaseJob(tr_main, 0)) assert len(runner.killed_by_dependency) == 1 assert runner.killed_by_dependency[0].test_run == tr_dep diff --git a/uv.lock b/uv.lock index 38543764d..3bd37f9da 100644 --- a/uv.lock +++ b/uv.lock @@ -106,15 +106,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] -[[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 = "beautifulsoup4" version = "4.14.3" @@ -301,7 +292,6 @@ dev = [ { name = "pandas-stubs" }, { name = "pyright" }, { name = "pytest" }, - { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-deadfixtures" }, { name = "ruff" }, @@ -333,7 +323,7 @@ requires-dist = [ { name = "build", marker = "extra == 'dev'", specifier = "~=1.4" }, { name = "click", specifier = "~=8.3" }, { name = "huggingface-hub", specifier = "~=1.4" }, - { name = "import-linter", marker = "extra == 'dev'", specifier = "~=2.10" }, + { name = "import-linter", marker = "extra == 'dev'", specifier = "~=2.5" }, { name = "jinja2", specifier = "~=3.1.6" }, { name = "kubernetes", specifier = "~=35.0" }, { name = "nvidia-sphinx-theme", marker = "extra == 'docs'", specifier = "~=0.0.8" }, @@ -342,9 +332,8 @@ requires-dist = [ { name = "pydantic", specifier = "~=2.12" }, { name = "pyright", marker = "extra == 'dev'", specifier = "~=1.1" }, { name = "pytest", marker = "extra == 'dev'", specifier = "~=9.0" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = "~=1.3" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = "~=7.0" }, - { name = "pytest-deadfixtures", marker = "extra == 'dev'", specifier = "~=3.1" }, + { name = "pytest-deadfixtures", marker = "extra == 'dev'", specifier = "~=2.2" }, { name = "rich", specifier = "~=14.3" }, { name = "ruff", marker = "extra == 'dev'", specifier = "~=0.15" }, { name = "sphinx", marker = "extra == 'docs'", specifier = "~=8.1" }, @@ -2046,20 +2035,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] -[[package]] -name = "pytest-asyncio" -version = "1.3.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/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - [[package]] name = "pytest-cov" version = "7.0.0" @@ -2076,14 +2051,14 @@ wheels = [ [[package]] name = "pytest-deadfixtures" -version = "3.1.0" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/b5/6fc6a3096e5bc3124c61af17c53d09290a5bfbca51fb8b94068d1e770d0e/pytest_deadfixtures-3.1.0.tar.gz", hash = "sha256:a8010e771183176fa9d918c8c49293ca5568597c8e1453ed2bbd41bcee67bde5", size = 9294, upload-time = "2026-01-15T20:05:40.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/d5/0472e7dd4c5794cd720f8add3ba59b35e45c1bb7482e04b6d6e60ff4eed0/pytest-deadfixtures-2.2.1.tar.gz", hash = "sha256:ca15938a4e8330993ccec9c6c847383d88b3cd574729530647dc6b492daa9c1e", size = 6616, upload-time = "2020-07-23T11:58:09.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/d0/67f83859706d11717498e1ef5e75f1c61835fd972c902f5bc9cbd9cb5659/pytest_deadfixtures-3.1.0-py2.py3-none-any.whl", hash = "sha256:898ba131c7472bd05551b73e2b99a51898fc3b8255c0589cf8b3c3c804e8f502", size = 7070, upload-time = "2026-01-15T20:05:39.599Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c7/d8c3fa6d2de6814c92161fcce984a7d38a63186590a66238e7b749f7fe37/pytest_deadfixtures-2.2.1-py2.py3-none-any.whl", hash = "sha256:db71533f2d9456227084e00a1231e732973e299ccb7c37ab92e95032ab6c083e", size = 5059, upload-time = "2020-07-23T11:58:11.538Z" }, ] [[package]]