diff --git a/src/runloop_api_client/sdk/__init__.py b/src/runloop_api_client/sdk/__init__.py index 483f4b711..1b95e585b 100644 --- a/src/runloop_api_client/sdk/__init__.py +++ b/src/runloop_api_client/sdk/__init__.py @@ -5,30 +5,35 @@ from __future__ import annotations -from .sync import AgentOps, DevboxOps, ScorerOps, RunloopSDK, SnapshotOps, BlueprintOps, StorageObjectOps +from .sync import AgentOps, DevboxOps, ScorerOps, RunloopSDK, ScenarioOps, SnapshotOps, BlueprintOps, StorageObjectOps from .agent import Agent from .async_ import ( AsyncAgentOps, AsyncDevboxOps, AsyncScorerOps, AsyncRunloopSDK, + AsyncScenarioOps, AsyncSnapshotOps, AsyncBlueprintOps, AsyncStorageObjectOps, ) from .devbox import Devbox, NamedShell from .scorer import Scorer +from .scenario import Scenario from .snapshot import Snapshot from .blueprint import Blueprint from .execution import Execution from .async_agent import AsyncAgent from .async_devbox import AsyncDevbox, AsyncNamedShell from .async_scorer import AsyncScorer +from .scenario_run import ScenarioRun +from .async_scenario import AsyncScenario from .async_snapshot import AsyncSnapshot from .storage_object import StorageObject from .async_blueprint import AsyncBlueprint from .async_execution import AsyncExecution from .execution_result import ExecutionResult +from .async_scenario_run import AsyncScenarioRun from .async_storage_object import AsyncStorageObject from .async_execution_result import AsyncExecutionResult @@ -43,6 +48,8 @@ "AsyncDevboxOps", "BlueprintOps", "AsyncBlueprintOps", + "ScenarioOps", + "AsyncScenarioOps", "ScorerOps", "AsyncScorerOps", "SnapshotOps", @@ -60,6 +67,10 @@ "AsyncExecutionResult", "Blueprint", "AsyncBlueprint", + "Scenario", + "AsyncScenario", + "ScenarioRun", + "AsyncScenarioRun", "Scorer", "AsyncScorer", "Snapshot", diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index 4432c07a5..31d543abb 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -11,10 +11,13 @@ from ..types.agent_create_params import AgentCreateParams from ..types.devbox_create_params import DevboxCreateParams, DevboxBaseCreateParams from ..types.object_create_params import ObjectCreateParams +from ..types.scenario_list_params import ScenarioListParams from ..types.blueprint_list_params import BlueprintListParams from ..types.object_download_params import ObjectDownloadParams +from ..types.scenario_update_params import ScenarioUpdateParams from ..types.blueprint_create_params import BlueprintCreateParams from ..types.devbox_upload_file_params import DevboxUploadFileParams +from ..types.scenario_start_run_params import ScenarioStartRunBaseParams from ..types.devbox_create_tunnel_params import DevboxCreateTunnelParams from ..types.devbox_download_file_params import DevboxDownloadFileParams from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams @@ -167,3 +170,19 @@ class SDKAgentCreateParams(AgentCreateParams, LongRequestOptions): class SDKAgentListParams(AgentListParams, BaseRequestOptions): pass + + +class SDKScenarioListParams(ScenarioListParams, BaseRequestOptions): + pass + + +class SDKScenarioUpdateParams(ScenarioUpdateParams, LongRequestOptions): + pass + + +class SDKScenarioRunAsyncParams(ScenarioStartRunBaseParams, LongRequestOptions): + pass + + +class SDKScenarioRunParams(ScenarioStartRunBaseParams, LongPollingRequestOptions): + pass diff --git a/src/runloop_api_client/sdk/agent.py b/src/runloop_api_client/sdk/agent.py index e0b0e0ec4..10361ff94 100644 --- a/src/runloop_api_client/sdk/agent.py +++ b/src/runloop_api_client/sdk/agent.py @@ -19,10 +19,7 @@ class Agent: including retrieving agent information. Example: - >>> agent = runloop.agent.create_from_npm( - ... name="my-agent", - ... package_name="@runloop/example-agent" - ... ) + >>> agent = runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent") >>> info = agent.get_info() >>> print(info.name) """ diff --git a/src/runloop_api_client/sdk/async_.py b/src/runloop_api_client/sdk/async_.py index 7d2ebd836..b4c0fb61f 100644 --- a/src/runloop_api_client/sdk/async_.py +++ b/src/runloop_api_client/sdk/async_.py @@ -21,6 +21,7 @@ SDKAgentCreateParams, SDKDevboxCreateParams, SDKObjectCreateParams, + SDKScenarioListParams, SDKScorerCreateParams, SDKBlueprintListParams, SDKBlueprintCreateParams, @@ -33,6 +34,7 @@ from .async_agent import AsyncAgent from .async_devbox import AsyncDevbox from .async_scorer import AsyncScorer +from .async_scenario import AsyncScenario from .async_snapshot import AsyncSnapshot from .async_blueprint import AsyncBlueprint from .async_storage_object import AsyncStorageObject @@ -543,7 +545,8 @@ async def list(self, **params: Unpack[SDKScorerListParams]) -> list[AsyncScorer] """ page = await self._client.scenarios.scorers.list(**params) return [AsyncScorer(self._client, item.id) async for item in page] - + + class AsyncAgentOps: """High-level async manager for creating and managing agents. @@ -553,15 +556,10 @@ class AsyncAgentOps: Example: >>> runloop = AsyncRunloopSDK() >>> # Create agent from NPM package - >>> agent = await runloop.agent.create_from_npm( - ... name="my-agent", - ... package_name="@runloop/example-agent" - ... ) + >>> agent = await runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent") >>> # Create agent from Git repository >>> agent = await runloop.agent.create_from_git( - ... name="git-agent", - ... repository="https://github.com/user/agent-repo", - ... ref="main" + ... name="git-agent", repository="https://github.com/user/agent-repo", ref="main" ... ) >>> # List all agents >>> agents = await runloop.agent.list(limit=10) @@ -615,7 +613,9 @@ async def create_from_npm( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration" + ) npm_config: dict = {"package_name": package_name} if npm_version is not None: @@ -655,7 +655,9 @@ async def create_from_pip( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration" + ) pip_config: dict = {"package_name": package_name} if pip_version is not None: @@ -692,7 +694,9 @@ async def create_from_git( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration" + ) git_config: dict = {"repository": repository} if ref is not None: @@ -724,7 +728,9 @@ async def create_from_object( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration" + ) object_config: dict = {"object_id": object_id} if agent_setup is not None: @@ -761,6 +767,45 @@ async def list( return [AsyncAgent(self._client, item.id, item) for item in page.agents] +class AsyncScenarioOps: + """Manage scenarios (async). Access via ``runloop.scenario``. + + Example: + >>> runloop = AsyncRunloopSDK() + >>> scenario = runloop.scenario.from_id("scn-xxx") + >>> run = await scenario.run() + >>> scenarios = await runloop.scenario.list() + """ + + def __init__(self, client: AsyncRunloop) -> None: + """Initialize AsyncScenarioOps. + + :param client: AsyncRunloop client instance + :type client: AsyncRunloop + """ + self._client = client + + def from_id(self, scenario_id: str) -> AsyncScenario: + """Get an AsyncScenario instance for an existing scenario ID. + + :param scenario_id: ID of the scenario + :type scenario_id: str + :return: AsyncScenario instance for the given ID + :rtype: AsyncScenario + """ + return AsyncScenario(self._client, scenario_id) + + async def list(self, **params: Unpack[SDKScenarioListParams]) -> list[AsyncScenario]: + """List all scenarios, optionally filtered by parameters. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters + :return: List of scenarios + :rtype: list[AsyncScenario] + """ + page = await self._client.scenarios.list(**params) + return [AsyncScenario(self._client, item.id) async for item in page] + + class AsyncRunloopSDK: """High-level asynchronous entry point for the Runloop SDK. @@ -776,6 +821,8 @@ class AsyncRunloopSDK: :vartype devbox: AsyncDevboxOps :ivar blueprint: High-level async interface for blueprint management :vartype blueprint: AsyncBlueprintOps + :ivar scenario: High-level async interface for scenario management + :vartype scenario: AsyncScenarioOps :ivar scorer: High-level async interface for scorer management :vartype scorer: AsyncScorerOps :ivar snapshot: High-level async interface for snapshot management @@ -795,6 +842,7 @@ class AsyncRunloopSDK: agent: AsyncAgentOps devbox: AsyncDevboxOps blueprint: AsyncBlueprintOps + scenario: AsyncScenarioOps scorer: AsyncScorerOps snapshot: AsyncSnapshotOps storage_object: AsyncStorageObjectOps @@ -840,6 +888,7 @@ def __init__( self.agent = AsyncAgentOps(self.api) self.devbox = AsyncDevboxOps(self.api) self.blueprint = AsyncBlueprintOps(self.api) + self.scenario = AsyncScenarioOps(self.api) self.scorer = AsyncScorerOps(self.api) self.snapshot = AsyncSnapshotOps(self.api) self.storage_object = AsyncStorageObjectOps(self.api) diff --git a/src/runloop_api_client/sdk/async_agent.py b/src/runloop_api_client/sdk/async_agent.py index eefce2120..ea59ced26 100644 --- a/src/runloop_api_client/sdk/async_agent.py +++ b/src/runloop_api_client/sdk/async_agent.py @@ -19,10 +19,7 @@ class AsyncAgent: including retrieving agent information. Example: - >>> agent = await runloop.agent.create_from_npm( - ... name="my-agent", - ... package_name="@runloop/example-agent" - ... ) + >>> agent = await runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent") >>> info = await agent.get_info() >>> print(info.name) """ diff --git a/src/runloop_api_client/sdk/async_scenario.py b/src/runloop_api_client/sdk/async_scenario.py new file mode 100644 index 000000000..02ee8f7f3 --- /dev/null +++ b/src/runloop_api_client/sdk/async_scenario.py @@ -0,0 +1,118 @@ +"""AsyncScenario resource class for asynchronous operations.""" + +from __future__ import annotations + +from typing_extensions import Unpack, override + +from ..types import ScenarioView +from ._types import BaseRequestOptions, SDKScenarioRunParams, SDKScenarioUpdateParams, SDKScenarioRunAsyncParams +from .._client import AsyncRunloop +from .async_scenario_run import AsyncScenarioRun + + +class AsyncScenario: + """A scenario for evaluating agent performance (async). + + Provides async methods for retrieving scenario details, updating the scenario, + and starting scenario runs. Obtain instances via ``runloop.scenario.from_id()`` + or ``runloop.scenario.list()``. + + Example: + >>> scenario = runloop.scenario.from_id("scn-xxx") + >>> info = await scenario.get_info() + >>> run = await scenario.run(run_name="test-run") + """ + + def __init__(self, client: AsyncRunloop, scenario_id: str) -> None: + """Create an AsyncScenario instance. + + :param client: AsyncRunloop client instance + :type client: AsyncRunloop + :param scenario_id: Scenario ID + :type scenario_id: str + """ + self._client = client + self._id = scenario_id + + @override + def __repr__(self) -> str: + return f"" + + @property + def id(self) -> str: + """Return the scenario ID. + + :return: Unique scenario ID + :rtype: str + """ + return self._id + + async def get_info( + self, + **options: Unpack[BaseRequestOptions], + ) -> ScenarioView: + """Retrieve current scenario details. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options + :return: Current scenario info + :rtype: ScenarioView + """ + return await self._client.scenarios.retrieve( + self._id, + **options, + ) + + async def update( + self, + **params: Unpack[SDKScenarioUpdateParams], + ) -> ScenarioView: + """Update the scenario. + + Only provided fields will be updated. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioUpdateParams` for available parameters + :return: Updated scenario info + :rtype: ScenarioView + """ + return await self._client.scenarios.update( + self._id, + **params, + ) + + async def run_async( + self, + **params: Unpack[SDKScenarioRunAsyncParams], + ) -> AsyncScenarioRun: + """Start a new scenario run without waiting for the devbox. + + Creates a new scenario run and returns immediately. The devbox may still + be starting; call ``await_env_ready()`` on the returned AsyncScenarioRun + to wait for it to be ready. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunAsyncParams` for available parameters + :return: AsyncScenarioRun instance for managing the run + :rtype: AsyncScenarioRun + """ + run_view = await self._client.scenarios.start_run( + scenario_id=self._id, + **params, + ) + return AsyncScenarioRun(self._client, run_view.id, run_view.devbox_id) + + async def run( + self, + **params: Unpack[SDKScenarioRunParams], + ) -> AsyncScenarioRun: + """Start a new scenario run and wait for the devbox to be ready. + + Convenience method that starts a run and waits for the devbox to be ready. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunParams` for available parameters + :return: AsyncScenarioRun instance with ready devbox + :rtype: AsyncScenarioRun + """ + run_view = await self._client.scenarios.start_run_and_await_env_ready( + scenario_id=self._id, + **params, + ) + return AsyncScenarioRun(self._client, run_view.id, run_view.devbox_id) diff --git a/src/runloop_api_client/sdk/async_scenario_run.py b/src/runloop_api_client/sdk/async_scenario_run.py new file mode 100644 index 000000000..314de676f --- /dev/null +++ b/src/runloop_api_client/sdk/async_scenario_run.py @@ -0,0 +1,242 @@ +"""AsyncScenarioRun resource class for asynchronous operations.""" + +from __future__ import annotations + +import os +from typing import Union, Optional +from functools import cached_property +from typing_extensions import Unpack, override + +from ..types import ScenarioRunView +from ._types import BaseRequestOptions, LongRequestOptions, PollingRequestOptions +from .._client import AsyncRunloop +from ._helpers import filter_params +from .async_devbox import AsyncDevbox +from ..types.scoring_contract_result_view import ScoringContractResultView + + +class AsyncScenarioRun: + """A running scenario with devbox access (async). + + Provides async methods for managing the scenario run lifecycle, accessing + the devbox, and retrieving scoring results. Obtain instances via + ``scenario.run()`` or ``scenario.run_async()``. + + Example: + >>> scenario = runloop.scenario.from_id("scn-xxx") + >>> run = await scenario.run_async() + >>> await run.await_env_ready() + >>> devbox = run.devbox + >>> # ... agent does work on the devbox ... + >>> await run.score_and_await() + >>> score = await run.get_score() + """ + + def __init__(self, client: AsyncRunloop, run_id: str, devbox_id: str) -> None: + """Create an AsyncScenarioRun instance. + + :param client: AsyncRunloop client instance + :type client: AsyncRunloop + :param run_id: Scenario run ID + :type run_id: str + :param devbox_id: Devbox ID associated with this run + :type devbox_id: str + """ + self._client = client + self._id = run_id + self._devbox_id = devbox_id + + @override + def __repr__(self) -> str: + return f"" + + @property + def id(self) -> str: + """Return the scenario run ID. + + :return: Unique scenario run ID + :rtype: str + """ + return self._id + + @property + def devbox_id(self) -> str: + """Return the devbox ID associated with this run. + + :return: Devbox ID + :rtype: str + """ + return self._devbox_id + + @cached_property + def devbox(self) -> AsyncDevbox: + """The devbox instance for this scenario run. + + Use this to interact with the devbox environment during the scenario run. + + :return: AsyncDevbox instance + :rtype: AsyncDevbox + """ + return AsyncDevbox(self._client, self._devbox_id) + + async def get_info( + self, + **options: Unpack[BaseRequestOptions], + ) -> ScenarioRunView: + """Retrieve current scenario run status and metadata. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options + :return: Current scenario run state info + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.retrieve( + self._id, + **options, + ) + + async def await_env_ready( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Wait for the scenario environment (devbox) to be ready. + + Blocks until the devbox reaches running state. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Scenario run state after environment is ready + :rtype: ScenarioRunView + """ + await self._client.devboxes.await_running(self._devbox_id, polling_config=options.get("polling_config")) + return await self.get_info(**filter_params(options, BaseRequestOptions)) + + async def score( + self, + **options: Unpack[LongRequestOptions], + ) -> ScenarioRunView: + """Submit the scenario run for scoring. + + This triggers the scoring process using the scenario's scoring contract. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + :return: Updated scenario run state + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.score( + self._id, + **options, + ) + + async def await_scored( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Wait for the scenario run to be scored. + + Blocks until scoring is complete. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Scored scenario run state + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.await_scored( + self._id, + **options, + ) + + async def score_and_await( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Submit for scoring and wait for completion. + + Convenience method that calls score() then await_scored(). + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Scored scenario run state + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.score_and_await( + self._id, + **options, + ) + + async def score_and_complete( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Score the run, wait for scoring, then complete and shutdown. + + Convenience method that scores the scenario run, waits for scoring to + finish, then completes the run and shuts down the devbox. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Completed scenario run state with scoring results + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.score_and_complete( + self._id, + **options, + ) + + async def complete( + self, + **options: Unpack[LongRequestOptions], + ) -> ScenarioRunView: + """Complete the scenario run and shutdown the devbox. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + :return: Final scenario run state + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.complete( + self._id, + **options, + ) + + async def cancel( + self, + **options: Unpack[LongRequestOptions], + ) -> ScenarioRunView: + """Cancel the scenario run and shutdown the devbox. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + :return: Cancelled scenario run state + :rtype: ScenarioRunView + """ + return await self._client.scenarios.runs.cancel( + self._id, + **options, + ) + + async def download_logs( + self, + file: Union[str, os.PathLike[str]], + **options: Unpack[LongRequestOptions], + ) -> None: + """Download all logs for this scenario run to a zip file. + + Downloads a zip archive containing all logs from the scenario run's + associated devbox. + + :param file: Path where the zip file will be written + :type file: str | os.PathLike[str] + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + """ + response = await self._client.scenarios.runs.download_logs(self._id, **options) + await response.write_to_file(file) + + async def get_score( + self, + **options: Unpack[BaseRequestOptions], + ) -> Optional[ScoringContractResultView]: + """Get the scoring result for this run. + + Returns None if the run has not been scored yet. Always makes an API + call to retrieve the current scoring result. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options + :return: Scoring result or None if not yet scored + :rtype: Optional[ScoringContractResultView] + """ + info = await self.get_info(**options) + return info.scoring_contract_result diff --git a/src/runloop_api_client/sdk/scenario.py b/src/runloop_api_client/sdk/scenario.py new file mode 100644 index 000000000..346118b91 --- /dev/null +++ b/src/runloop_api_client/sdk/scenario.py @@ -0,0 +1,118 @@ +"""Scenario resource class for synchronous operations.""" + +from __future__ import annotations + +from typing_extensions import Unpack, override + +from ..types import ScenarioView +from ._types import BaseRequestOptions, SDKScenarioRunParams, SDKScenarioUpdateParams, SDKScenarioRunAsyncParams +from .._client import Runloop +from .scenario_run import ScenarioRun + + +class Scenario: + """A scenario for evaluating agent performance. + + Provides methods for retrieving scenario details, updating the scenario, + and starting scenario runs. Obtain instances via ``runloop.scenario.from_id()`` + or ``runloop.scenario.list()``. + + Example: + >>> scenario = runloop.scenario.from_id("scn-xxx") + >>> info = scenario.get_info() + >>> run = scenario.run(run_name="test-run") + """ + + def __init__(self, client: Runloop, scenario_id: str) -> None: + """Create a Scenario instance. + + :param client: Runloop client instance + :type client: Runloop + :param scenario_id: Scenario ID + :type scenario_id: str + """ + self._client = client + self._id = scenario_id + + @override + def __repr__(self) -> str: + return f"" + + @property + def id(self) -> str: + """Return the scenario ID. + + :return: Unique scenario ID + :rtype: str + """ + return self._id + + def get_info( + self, + **options: Unpack[BaseRequestOptions], + ) -> ScenarioView: + """Retrieve current scenario details. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options + :return: Current scenario info + :rtype: ScenarioView + """ + return self._client.scenarios.retrieve( + self._id, + **options, + ) + + def update( + self, + **params: Unpack[SDKScenarioUpdateParams], + ) -> ScenarioView: + """Update the scenario. + + Only provided fields will be updated. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioUpdateParams` for available parameters + :return: Updated scenario info + :rtype: ScenarioView + """ + return self._client.scenarios.update( + self._id, + **params, + ) + + def run_async( + self, + **params: Unpack[SDKScenarioRunAsyncParams], + ) -> ScenarioRun: + """Start a new scenario run without waiting for the devbox. + + Creates a new scenario run and returns immediately. The devbox may still + be starting; call ``await_env_ready()`` on the returned ScenarioRun to + wait for it to be ready. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunAsyncParams` for available parameters + :return: ScenarioRun instance for managing the run + :rtype: ScenarioRun + """ + run_view = self._client.scenarios.start_run( + scenario_id=self._id, + **params, + ) + return ScenarioRun(self._client, run_view.id, run_view.devbox_id) + + def run( + self, + **params: Unpack[SDKScenarioRunParams], + ) -> ScenarioRun: + """Start a new scenario run and wait for the devbox to be ready. + + Convenience method that starts a run and waits for the devbox to be ready. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunParams` for available parameters + :return: ScenarioRun instance with ready devbox + :rtype: ScenarioRun + """ + run_view = self._client.scenarios.start_run_and_await_env_ready( + scenario_id=self._id, + **params, + ) + return ScenarioRun(self._client, run_view.id, run_view.devbox_id) diff --git a/src/runloop_api_client/sdk/scenario_run.py b/src/runloop_api_client/sdk/scenario_run.py new file mode 100644 index 000000000..ede44b105 --- /dev/null +++ b/src/runloop_api_client/sdk/scenario_run.py @@ -0,0 +1,242 @@ +"""ScenarioRun resource class for synchronous operations.""" + +from __future__ import annotations + +import os +from typing import Union, Optional +from functools import cached_property +from typing_extensions import Unpack, override + +from ..types import ScenarioRunView +from ._types import BaseRequestOptions, LongRequestOptions, PollingRequestOptions +from .devbox import Devbox +from .._client import Runloop +from ._helpers import filter_params +from ..types.scoring_contract_result_view import ScoringContractResultView + + +class ScenarioRun: + """A running scenario with devbox access. + + Provides methods for managing the scenario run lifecycle, accessing + the devbox, and retrieving scoring results. Obtain instances via + ``scenario.run()`` or ``scenario.run_async()``. + + Example: + >>> scenario = runloop.scenario.from_id("scn-xxx") + >>> run = scenario.run_async() + >>> run.await_env_ready() + >>> devbox = run.devbox + >>> # ... agent does work on the devbox ... + >>> run.score_and_await() + >>> score = run.get_score() + """ + + def __init__(self, client: Runloop, run_id: str, devbox_id: str) -> None: + """Create a ScenarioRun instance. + + :param client: Runloop client instance + :type client: Runloop + :param run_id: Scenario run ID + :type run_id: str + :param devbox_id: Devbox ID associated with this run + :type devbox_id: str + """ + self._client = client + self._id = run_id + self._devbox_id = devbox_id + + @override + def __repr__(self) -> str: + return f"" + + @property + def id(self) -> str: + """Return the scenario run ID. + + :return: Unique scenario run ID + :rtype: str + """ + return self._id + + @property + def devbox_id(self) -> str: + """Return the devbox ID associated with this run. + + :return: Devbox ID + :rtype: str + """ + return self._devbox_id + + @cached_property + def devbox(self) -> Devbox: + """The devbox instance for this scenario run. + + Use this to interact with the devbox environment during the scenario run. + + :return: Devbox instance + :rtype: Devbox + """ + return Devbox(self._client, self._devbox_id) + + def get_info( + self, + **options: Unpack[BaseRequestOptions], + ) -> ScenarioRunView: + """Retrieve current scenario run status and metadata. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options + :return: Current scenario run state info + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.retrieve( + self._id, + **options, + ) + + def await_env_ready( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Wait for the scenario environment (devbox) to be ready. + + Blocks until the devbox reaches running state. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Scenario run state after environment is ready + :rtype: ScenarioRunView + """ + self._client.devboxes.await_running(self._devbox_id, polling_config=options.get("polling_config")) + return self.get_info(**filter_params(options, BaseRequestOptions)) + + def score( + self, + **options: Unpack[LongRequestOptions], + ) -> ScenarioRunView: + """Submit the scenario run for scoring. + + This triggers the scoring process using the scenario's scoring contract. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + :return: Updated scenario run state + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.score( + self._id, + **options, + ) + + def await_scored( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Wait for the scenario run to be scored. + + Blocks until scoring is complete. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Scored scenario run state + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.await_scored( + self._id, + **options, + ) + + def score_and_await( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Submit for scoring and wait for completion. + + Convenience method that calls score() then await_scored(). + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Scored scenario run state + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.score_and_await( + self._id, + **options, + ) + + def score_and_complete( + self, + **options: Unpack[PollingRequestOptions], + ) -> ScenarioRunView: + """Score the run, wait for scoring, then complete and shutdown. + + Convenience method that scores the scenario run, waits for scoring to + finish, then completes the run and shuts down the devbox. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options + :return: Completed scenario run state with scoring results + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.score_and_complete( + self._id, + **options, + ) + + def complete( + self, + **options: Unpack[LongRequestOptions], + ) -> ScenarioRunView: + """Complete the scenario run and shutdown the devbox. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + :return: Final scenario run state + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.complete( + self._id, + **options, + ) + + def cancel( + self, + **options: Unpack[LongRequestOptions], + ) -> ScenarioRunView: + """Cancel the scenario run and shutdown the devbox. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + :return: Cancelled scenario run state + :rtype: ScenarioRunView + """ + return self._client.scenarios.runs.cancel( + self._id, + **options, + ) + + def download_logs( + self, + file: Union[str, os.PathLike[str]], + **options: Unpack[LongRequestOptions], + ) -> None: + """Download all logs for this scenario run to a zip file. + + Downloads a zip archive containing all logs from the scenario run's + associated devbox. + + :param file: Path where the zip file will be written + :type file: str | os.PathLike[str] + :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options + """ + response = self._client.scenarios.runs.download_logs(self._id, **options) + response.write_to_file(file) + + def get_score( + self, + **options: Unpack[BaseRequestOptions], + ) -> Optional[ScoringContractResultView]: + """Get the scoring result for this run. + + Returns None if the run has not been scored yet. Always makes an API + call to retrieve the current scoring result. + + :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options + :return: Scoring result or None if not yet scored + :rtype: Optional[ScoringContractResultView] + """ + info = self.get_info(**options) + return info.scoring_contract_result diff --git a/src/runloop_api_client/sdk/sync.py b/src/runloop_api_client/sdk/sync.py index 8c4da60af..077a99a92 100644 --- a/src/runloop_api_client/sdk/sync.py +++ b/src/runloop_api_client/sdk/sync.py @@ -21,6 +21,7 @@ SDKAgentCreateParams, SDKDevboxCreateParams, SDKObjectCreateParams, + SDKScenarioListParams, SDKScorerCreateParams, SDKBlueprintListParams, SDKBlueprintCreateParams, @@ -32,6 +33,7 @@ from .._types import Timeout, NotGiven, not_given from .._client import DEFAULT_MAX_RETRIES, Runloop from ._helpers import detect_content_type +from .scenario import Scenario from .snapshot import Snapshot from .blueprint import Blueprint from .storage_object import StorageObject @@ -539,7 +541,7 @@ def list(self, **params: Unpack[SDKScorerListParams]) -> list[Scorer]: page = self._client.scenarios.scorers.list(**params) return [Scorer(self._client, item.id) for item in page] - + class AgentOps: """High-level manager for creating and managing agents. @@ -549,15 +551,10 @@ class AgentOps: Example: >>> runloop = RunloopSDK() >>> # Create agent from NPM package - >>> agent = runloop.agent.create_from_npm( - ... name="my-agent", - ... package_name="@runloop/example-agent" - ... ) + >>> agent = runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent") >>> # Create agent from Git repository >>> agent = runloop.agent.create_from_git( - ... name="git-agent", - ... repository="https://github.com/user/agent-repo", - ... ref="main" + ... name="git-agent", repository="https://github.com/user/agent-repo", ref="main" ... ) >>> # List all agents >>> agents = runloop.agent.list(limit=10) @@ -599,9 +596,7 @@ def create_from_npm( Example: >>> agent = runloop.agent.create_from_npm( - ... name="my-npm-agent", - ... package_name="@runloop/example-agent", - ... npm_version="^1.0.0" + ... name="my-npm-agent", package_name="@runloop/example-agent", npm_version="^1.0.0" ... ) :param package_name: NPM package name @@ -618,7 +613,9 @@ def create_from_npm( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration" + ) npm_config: dict = {"package_name": package_name} if npm_version is not None: @@ -646,9 +643,7 @@ def create_from_pip( Example: >>> agent = runloop.agent.create_from_pip( - ... name="my-pip-agent", - ... package_name="runloop-example-agent", - ... pip_version=">=1.0.0" + ... name="my-pip-agent", package_name="runloop-example-agent", pip_version=">=1.0.0" ... ) :param package_name: Pip package name @@ -665,7 +660,9 @@ def create_from_pip( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration" + ) pip_config: dict = {"package_name": package_name} if pip_version is not None: @@ -695,7 +692,7 @@ def create_from_git( ... name="my-git-agent", ... repository="https://github.com/user/agent-repo", ... ref="main", - ... agent_setup=["npm install", "npm run build"] + ... agent_setup=["npm install", "npm run build"], ... ) :param repository: Git repository URL @@ -710,7 +707,9 @@ def create_from_git( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration" + ) git_config: dict = {"repository": repository} if ref is not None: @@ -737,9 +736,7 @@ def create_from_object( >>> obj = runloop.storage_object.upload_from_dir("./my-agent") >>> # Then create agent from the object >>> agent = runloop.agent.create_from_object( - ... name="my-object-agent", - ... object_id=obj.id, - ... agent_setup=["chmod +x setup.sh", "./setup.sh"] + ... name="my-object-agent", object_id=obj.id, agent_setup=["chmod +x setup.sh", "./setup.sh"] ... ) :param object_id: Storage object ID @@ -752,7 +749,9 @@ def create_from_object( :raises ValueError: If 'source' is provided in params """ if "source" in params: - raise ValueError("Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration") + raise ValueError( + "Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration" + ) object_config: dict = {"object_id": object_id} if agent_setup is not None: @@ -789,6 +788,45 @@ def list( return [Agent(self._client, item.id, item) for item in page.agents] +class ScenarioOps: + """Manage scenarios. Access via ``runloop.scenario``. + + Example: + >>> runloop = RunloopSDK() + >>> scenario = runloop.scenario.from_id("scn-xxx") + >>> run = scenario.run() + >>> scenarios = runloop.scenario.list() + """ + + def __init__(self, client: Runloop) -> None: + """Initialize ScenarioOps. + + :param client: Runloop client instance + :type client: Runloop + """ + self._client = client + + def from_id(self, scenario_id: str) -> Scenario: + """Get a Scenario instance for an existing scenario ID. + + :param scenario_id: ID of the scenario + :type scenario_id: str + :return: Scenario instance for the given ID + :rtype: Scenario + """ + return Scenario(self._client, scenario_id) + + def list(self, **params: Unpack[SDKScenarioListParams]) -> list[Scenario]: + """List all scenarios, optionally filtered by parameters. + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters + :return: List of scenarios + :rtype: list[Scenario] + """ + page = self._client.scenarios.list(**params) + return [Scenario(self._client, item.id) for item in page] + + class RunloopSDK: """High-level synchronous entry point for the Runloop SDK. @@ -804,6 +842,8 @@ class RunloopSDK: :vartype devbox: DevboxOps :ivar blueprint: High-level interface for blueprint management :vartype blueprint: BlueprintOps + :ivar scenario: High-level interface for scenario management + :vartype scenario: ScenarioOps :ivar scorer: High-level interface for scorer management :vartype scorer: ScorerOps :ivar snapshot: High-level interface for snapshot management @@ -823,6 +863,7 @@ class RunloopSDK: agent: AgentOps devbox: DevboxOps blueprint: BlueprintOps + scenario: ScenarioOps scorer: ScorerOps snapshot: SnapshotOps storage_object: StorageObjectOps @@ -868,6 +909,7 @@ def __init__( self.agent = AgentOps(self.api) self.devbox = DevboxOps(self.api) self.blueprint = BlueprintOps(self.api) + self.scenario = ScenarioOps(self.api) self.scorer = ScorerOps(self.api) self.snapshot = SnapshotOps(self.api) self.storage_object = StorageObjectOps(self.api) diff --git a/src/runloop_api_client/types/scenario_start_run_params.py b/src/runloop_api_client/types/scenario_start_run_params.py index 6f5e39c00..b6da90434 100644 --- a/src/runloop_api_client/types/scenario_start_run_params.py +++ b/src/runloop_api_client/types/scenario_start_run_params.py @@ -11,10 +11,7 @@ __all__ = ["ScenarioStartRunParams"] -class ScenarioStartRunParams(TypedDict, total=False): - scenario_id: Required[str] - """ID of the Scenario to run.""" - +class ScenarioStartRunBaseParams(TypedDict, total=False): benchmark_run_id: Optional[str] """Benchmark to associate the run.""" @@ -26,3 +23,8 @@ class ScenarioStartRunParams(TypedDict, total=False): run_profile: Annotated[Optional[RunProfile], PropertyInfo(alias="runProfile")] """Runtime configuration to use for this benchmark run""" + + +class ScenarioStartRunParams(ScenarioStartRunBaseParams): + scenario_id: Required[str] + """ID of the Scenario to run.""" diff --git a/tests/sdk/conftest.py b/tests/sdk/conftest.py index 6abf02684..c5546fe55 100644 --- a/tests/sdk/conftest.py +++ b/tests/sdk/conftest.py @@ -4,8 +4,8 @@ import asyncio import threading -from typing import Any -from dataclasses import dataclass +from typing import Any, Dict +from dataclasses import field, dataclass from unittest.mock import Mock, AsyncMock import httpx @@ -108,6 +108,27 @@ class MockAgentView: source: Any = None +@dataclass +class MockScenarioView: + """Mock ScenarioView for testing.""" + + id: str = "scn_123" + name: str = "test-scenario" + metadata: Dict[str, str] = field(default_factory=dict) + + +@dataclass +class MockScenarioRunView: + """Mock ScenarioRunView for testing.""" + + id: str = "run_123" + devbox_id: str = "dev_123" + scenario_id: str = "scn_123" + state: str = "running" + metadata: Dict[str, str] = field(default_factory=dict) + scoring_contract_result: object = None + + def create_mock_httpx_client(methods: dict[str, Any] | None = None) -> AsyncMock: """ Create a mock httpx.AsyncClient with proper context manager setup. @@ -204,6 +225,18 @@ def agent_view() -> MockAgentView: return MockAgentView() +@pytest.fixture +def scenario_view() -> MockScenarioView: + """Create a mock ScenarioView.""" + return MockScenarioView() + + +@pytest.fixture +def scenario_run_view() -> MockScenarioRunView: + """Create a mock ScenarioRunView.""" + return MockScenarioRunView() + + @pytest.fixture def mock_httpx_response() -> Mock: """Create a mock httpx.Response.""" @@ -240,9 +273,8 @@ def mock_async_stream() -> AsyncMock: """ async def async_iter(): - # Empty async iterator - if False: - yield + return + yield # Make this a generator stream = AsyncMock() stream.__aiter__ = Mock(return_value=async_iter()) diff --git a/tests/sdk/test_async_ops.py b/tests/sdk/test_async_ops.py index 8a455cd68..f64292ce6 100644 --- a/tests/sdk/test_async_ops.py +++ b/tests/sdk/test_async_ops.py @@ -15,6 +15,7 @@ MockDevboxView, MockObjectView, MockScorerView, + MockScenarioView, MockSnapshotView, MockBlueprintView, create_mock_httpx_response, @@ -730,7 +731,7 @@ async def async_iter(): assert scorers[1].id == "scorer_002" mock_async_client.scenarios.scorers.list.assert_awaited_once() - + class TestAsyncAgentClient: """Tests for AsyncAgentClient class.""" @@ -1067,6 +1068,82 @@ async def test_create_from_object_with_agent_setup( ) +class TestAsyncScenarioOps: + """Tests for AsyncScenarioOps class.""" + + def test_from_id(self, mock_async_client: AsyncMock) -> None: + """Test from_id method.""" + from runloop_api_client.sdk import AsyncScenario + from runloop_api_client.sdk.async_ import AsyncScenarioOps + + ops = AsyncScenarioOps(mock_async_client) + scenario = ops.from_id("scn_123") + + assert isinstance(scenario, AsyncScenario) + assert scenario.id == "scn_123" + + @pytest.mark.asyncio + async def test_list_empty(self, mock_async_client: AsyncMock) -> None: + """Test list method with empty results.""" + from runloop_api_client.sdk.async_ import AsyncScenarioOps + + async def async_iter(): + return + yield # Make this a generator + + mock_async_client.scenarios.list = AsyncMock(return_value=async_iter()) + + ops = AsyncScenarioOps(mock_async_client) + scenarios = await ops.list(limit=10) + + assert len(scenarios) == 0 + mock_async_client.scenarios.list.assert_awaited_once() + + @pytest.mark.asyncio + async def test_list_single(self, mock_async_client: AsyncMock, scenario_view: MockScenarioView) -> None: + """Test list method with single result.""" + from runloop_api_client.sdk import AsyncScenario + from runloop_api_client.sdk.async_ import AsyncScenarioOps + + async def async_iter(): + yield scenario_view + + mock_async_client.scenarios.list = AsyncMock(return_value=async_iter()) + + ops = AsyncScenarioOps(mock_async_client) + scenarios = await ops.list(limit=10) + + assert len(scenarios) == 1 + assert isinstance(scenarios[0], AsyncScenario) + assert scenarios[0].id == "scn_123" + mock_async_client.scenarios.list.assert_awaited_once() + + @pytest.mark.asyncio + async def test_list_multiple(self, mock_async_client: AsyncMock) -> None: + """Test list method with multiple results.""" + from runloop_api_client.sdk import AsyncScenario + from runloop_api_client.sdk.async_ import AsyncScenarioOps + + scenario_view1 = MockScenarioView(id="scn_001", name="scenario-1") + scenario_view2 = MockScenarioView(id="scn_002", name="scenario-2") + + async def async_iter(): + yield scenario_view1 + yield scenario_view2 + + mock_async_client.scenarios.list = AsyncMock(return_value=async_iter()) + + ops = AsyncScenarioOps(mock_async_client) + scenarios = await ops.list(limit=10) + + assert len(scenarios) == 2 + assert isinstance(scenarios[0], AsyncScenario) + assert isinstance(scenarios[1], AsyncScenario) + assert scenarios[0].id == "scn_001" + assert scenarios[1].id == "scn_002" + mock_async_client.scenarios.list.assert_awaited_once() + + class TestAsyncRunloopSDK: """Tests for AsyncRunloopSDK class.""" diff --git a/tests/sdk/test_async_scenario.py b/tests/sdk/test_async_scenario.py new file mode 100644 index 000000000..22a8f457a --- /dev/null +++ b/tests/sdk/test_async_scenario.py @@ -0,0 +1,121 @@ +"""Comprehensive tests for async AsyncScenario class.""" + +from __future__ import annotations + +from unittest.mock import AsyncMock + +from tests.sdk.conftest import MockScenarioView, MockScenarioRunView +from runloop_api_client.sdk import AsyncScenario + + +class TestAsyncScenario: + """Tests for AsyncScenario class.""" + + def test_init(self, mock_async_client: AsyncMock) -> None: + """Test AsyncScenario initialization.""" + scenario = AsyncScenario(mock_async_client, "scn_123") + assert scenario.id == "scn_123" + + def test_repr(self, mock_async_client: AsyncMock) -> None: + """Test AsyncScenario string representation.""" + scenario = AsyncScenario(mock_async_client, "scn_123") + assert repr(scenario) == "" + + async def test_get_info(self, mock_async_client: AsyncMock, scenario_view: MockScenarioView) -> None: + """Test get_info method.""" + mock_async_client.scenarios.retrieve = AsyncMock(return_value=scenario_view) + + scenario = AsyncScenario(mock_async_client, "scn_123") + result = await scenario.get_info() + + assert result == scenario_view + mock_async_client.scenarios.retrieve.assert_awaited_once_with("scn_123") + + async def test_update(self, mock_async_client: AsyncMock, scenario_view: MockScenarioView) -> None: + """Test update method.""" + mock_async_client.scenarios.update = AsyncMock(return_value=scenario_view) + + scenario = AsyncScenario(mock_async_client, "scn_123") + result = await scenario.update( + name="updated-scenario", + metadata={"env": "test"}, + environment_parameters={ + "blueprint_id": "bp_456", + }, + input_context={ + "problem_statement": "Updated problem statement", + }, + reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new", + required_environment_variables=["API_KEY", "DEBUG"], + required_secret_names=["DB_PASSWORD", "JWT_SECRET"], + scoring_contract={ + "scoring_function_parameters": [ + { + "name": "test-scorer", + "scorer": { + "type": "command_scorer", + "command": "echo 'score=1.0'", + }, + "weight": 1.0, + } + ], + }, + validation_type="FORWARD", + ) + + assert result == scenario_view + mock_async_client.scenarios.update.assert_awaited_once_with( + "scn_123", + name="updated-scenario", + metadata={"env": "test"}, + environment_parameters={ + "blueprint_id": "bp_456", + }, + input_context={ + "problem_statement": "Updated problem statement", + }, + reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new", + required_environment_variables=["API_KEY", "DEBUG"], + required_secret_names=["DB_PASSWORD", "JWT_SECRET"], + scoring_contract={ + "scoring_function_parameters": [ + { + "name": "test-scorer", + "scorer": { + "type": "command_scorer", + "command": "echo 'score=1.0'", + }, + "weight": 1.0, + } + ], + }, + validation_type="FORWARD", + ) + + async def test_run_async(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test run_async method returns AsyncScenarioRun wrapper.""" + mock_async_client.scenarios.start_run = AsyncMock(return_value=scenario_run_view) + + scenario = AsyncScenario(mock_async_client, "scn_123") + run = await scenario.run_async(run_name="test-run") + + assert run.id == "run_123" + assert run.devbox_id == "dev_123" + mock_async_client.scenarios.start_run.assert_awaited_once_with( + scenario_id="scn_123", + run_name="test-run", + ) + + async def test_run(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test run method.""" + mock_async_client.scenarios.start_run_and_await_env_ready = AsyncMock(return_value=scenario_run_view) + + scenario = AsyncScenario(mock_async_client, "scn_123") + run = await scenario.run(run_name="test-run") + + assert run.id == "run_123" + assert run.devbox_id == "dev_123" + mock_async_client.scenarios.start_run_and_await_env_ready.assert_awaited_once_with( + scenario_id="scn_123", + run_name="test-run", + ) diff --git a/tests/sdk/test_async_scenario_run.py b/tests/sdk/test_async_scenario_run.py new file mode 100644 index 000000000..010ad6cbb --- /dev/null +++ b/tests/sdk/test_async_scenario_run.py @@ -0,0 +1,162 @@ +"""Comprehensive tests for async AsyncScenarioRun class.""" + +from __future__ import annotations + +from types import SimpleNamespace +from pathlib import Path +from unittest.mock import AsyncMock + +from tests.sdk.conftest import MockDevboxView, MockScenarioRunView +from runloop_api_client.sdk import AsyncScenarioRun + + +class TestAsyncScenarioRun: + """Tests for AsyncScenarioRun class.""" + + def test_init(self, mock_async_client: AsyncMock) -> None: + """Test AsyncScenarioRun initialization.""" + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + assert run.id == "run_123" + assert run.devbox_id == "dev_123" + + def test_repr(self, mock_async_client: AsyncMock) -> None: + """Test AsyncScenarioRun string representation.""" + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + assert repr(run) == "" + + def test_devbox_property(self, mock_async_client: AsyncMock) -> None: + """Test devbox property returns AsyncDevbox wrapper.""" + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + devbox = run.devbox + + assert devbox.id == "dev_123" + + async def test_get_info(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test get_info method.""" + mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.get_info() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.retrieve.assert_awaited_once_with("run_123") + + async def test_await_env_ready( + self, + mock_async_client: AsyncMock, + scenario_run_view: MockScenarioRunView, + devbox_view: MockDevboxView, + ) -> None: + """Test await_env_ready method.""" + mock_async_client.devboxes.await_running = AsyncMock(return_value=devbox_view) + mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.await_env_ready() + + mock_async_client.devboxes.await_running.assert_awaited_once_with("dev_123", polling_config=None) + assert result == scenario_run_view + + async def test_score(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test score method.""" + scenario_run_view.state = "scoring" + mock_async_client.scenarios.runs.score = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.score() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.score.assert_awaited_once_with("run_123") + + async def test_await_scored(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test await_scored method.""" + scenario_run_view.state = "scored" + mock_async_client.scenarios.runs.await_scored = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.await_scored() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.await_scored.assert_awaited_once_with("run_123") + + async def test_score_and_await(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test score_and_await method.""" + scenario_run_view.state = "scored" + mock_async_client.scenarios.runs.score_and_await = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.score_and_await() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.score_and_await.assert_awaited_once_with("run_123") + + async def test_score_and_complete( + self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView + ) -> None: + """Test score_and_complete method.""" + scenario_run_view.state = "completed" + mock_async_client.scenarios.runs.score_and_complete = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.score_and_complete() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.score_and_complete.assert_awaited_once_with("run_123") + + async def test_complete(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test complete method.""" + scenario_run_view.state = "completed" + mock_async_client.scenarios.runs.complete = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.complete() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.complete.assert_awaited_once_with("run_123") + + async def test_cancel(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None: + """Test cancel method.""" + scenario_run_view.state = "canceled" + mock_async_client.scenarios.runs.cancel = AsyncMock(return_value=scenario_run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.cancel() + + assert result == scenario_run_view + mock_async_client.scenarios.runs.cancel.assert_awaited_once_with("run_123") + + async def test_download_logs(self, mock_async_client: AsyncMock, tmp_path: Path) -> None: + """Test download_logs method writes to file.""" + mock_response = AsyncMock() + mock_response.write_to_file = AsyncMock() + mock_async_client.scenarios.runs.download_logs = AsyncMock(return_value=mock_response) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + output_path = tmp_path / "logs.zip" + await run.download_logs(output_path) + + mock_async_client.scenarios.runs.download_logs.assert_awaited_once_with("run_123") + mock_response.write_to_file.assert_awaited_once_with(output_path) + + async def test_get_score_when_scored(self, mock_async_client: AsyncMock) -> None: + """Test get_score returns scoring result when scored.""" + scoring_result = SimpleNamespace(score=0.95, scoring_function_results=[]) + run_view = MockScenarioRunView(state="scored", scoring_contract_result=scoring_result) + mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.get_score() + + assert result == scoring_result + mock_async_client.scenarios.runs.retrieve.assert_awaited_once_with("run_123") + + async def test_get_score_when_not_scored(self, mock_async_client: AsyncMock) -> None: + """Test get_score returns None when not scored.""" + run_view = MockScenarioRunView(state="running", scoring_contract_result=None) + mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=run_view) + + run = AsyncScenarioRun(mock_async_client, "run_123", "dev_123") + result = await run.get_score() + + assert result is None + mock_async_client.scenarios.runs.retrieve.assert_awaited_once_with("run_123") diff --git a/tests/sdk/test_ops.py b/tests/sdk/test_ops.py index cf3a7216d..0136bd953 100644 --- a/tests/sdk/test_ops.py +++ b/tests/sdk/test_ops.py @@ -13,6 +13,7 @@ MockDevboxView, MockObjectView, MockScorerView, + MockScenarioView, MockSnapshotView, MockBlueprintView, create_mock_httpx_response, @@ -960,6 +961,67 @@ def test_create_from_object_with_agent_setup(self, mock_client: Mock, agent_view ) +class TestScenarioOps: + """Tests for ScenarioOps class.""" + + def test_from_id(self, mock_client: Mock) -> None: + """Test from_id method.""" + from runloop_api_client.sdk import Scenario + from runloop_api_client.sdk.sync import ScenarioOps + + ops = ScenarioOps(mock_client) + scenario = ops.from_id("scn_123") + + assert isinstance(scenario, Scenario) + assert scenario.id == "scn_123" + + def test_list_empty(self, mock_client: Mock) -> None: + """Test list method with empty results.""" + from runloop_api_client.sdk.sync import ScenarioOps + + mock_client.scenarios.list.return_value = [] + + ops = ScenarioOps(mock_client) + scenarios = ops.list(limit=10) + + assert len(scenarios) == 0 + mock_client.scenarios.list.assert_called_once() + + def test_list_single(self, mock_client: Mock, scenario_view: MockScenarioView) -> None: + """Test list method with single result.""" + from runloop_api_client.sdk import Scenario + from runloop_api_client.sdk.sync import ScenarioOps + + mock_client.scenarios.list.return_value = [scenario_view] + + ops = ScenarioOps(mock_client) + scenarios = ops.list(limit=10) + + assert len(scenarios) == 1 + assert isinstance(scenarios[0], Scenario) + assert scenarios[0].id == "scn_123" + mock_client.scenarios.list.assert_called_once() + + def test_list_multiple(self, mock_client: Mock) -> None: + """Test list method with multiple results.""" + from runloop_api_client.sdk import Scenario + from runloop_api_client.sdk.sync import ScenarioOps + + scenario_view1 = MockScenarioView(id="scn_001", name="scenario-1") + scenario_view2 = MockScenarioView(id="scn_002", name="scenario-2") + mock_client.scenarios.list.return_value = [scenario_view1, scenario_view2] + + ops = ScenarioOps(mock_client) + scenarios = ops.list(limit=10) + + assert len(scenarios) == 2 + assert isinstance(scenarios[0], Scenario) + assert isinstance(scenarios[1], Scenario) + assert scenarios[0].id == "scn_001" + assert scenarios[1].id == "scn_002" + mock_client.scenarios.list.assert_called_once() + + class TestRunloopSDK: """Tests for RunloopSDK class.""" diff --git a/tests/sdk/test_scenario.py b/tests/sdk/test_scenario.py new file mode 100644 index 000000000..3504c1714 --- /dev/null +++ b/tests/sdk/test_scenario.py @@ -0,0 +1,121 @@ +"""Comprehensive tests for sync Scenario class.""" + +from __future__ import annotations + +from unittest.mock import Mock + +from tests.sdk.conftest import MockScenarioView, MockScenarioRunView +from runloop_api_client.sdk import Scenario + + +class TestScenario: + """Tests for Scenario class.""" + + def test_init(self, mock_client: Mock) -> None: + """Test Scenario initialization.""" + scenario = Scenario(mock_client, "scn_123") + assert scenario.id == "scn_123" + + def test_repr(self, mock_client: Mock) -> None: + """Test Scenario string representation.""" + scenario = Scenario(mock_client, "scn_123") + assert repr(scenario) == "" + + def test_get_info(self, mock_client: Mock, scenario_view: MockScenarioView) -> None: + """Test get_info method.""" + mock_client.scenarios.retrieve.return_value = scenario_view + + scenario = Scenario(mock_client, "scn_123") + result = scenario.get_info() + + assert result == scenario_view + mock_client.scenarios.retrieve.assert_called_once_with("scn_123") + + def test_update(self, mock_client: Mock, scenario_view: MockScenarioView) -> None: + """Test update method.""" + mock_client.scenarios.update.return_value = scenario_view + + scenario = Scenario(mock_client, "scn_123") + result = scenario.update( + name="updated-scenario", + metadata={"env": "test"}, + environment_parameters={ + "blueprint_id": "bp_456", + }, + input_context={ + "problem_statement": "Updated problem statement", + }, + reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new", + required_environment_variables=["API_KEY", "DEBUG"], + required_secret_names=["DB_PASSWORD", "JWT_SECRET"], + scoring_contract={ + "scoring_function_parameters": [ + { + "name": "test-scorer", + "scorer": { + "type": "command_scorer", + "command": "echo 'score=1.0'", + }, + "weight": 1.0, + } + ], + }, + validation_type="FORWARD", + ) + + assert result == scenario_view + mock_client.scenarios.update.assert_called_once_with( + "scn_123", + name="updated-scenario", + metadata={"env": "test"}, + environment_parameters={ + "blueprint_id": "bp_456", + }, + input_context={ + "problem_statement": "Updated problem statement", + }, + reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new", + required_environment_variables=["API_KEY", "DEBUG"], + required_secret_names=["DB_PASSWORD", "JWT_SECRET"], + scoring_contract={ + "scoring_function_parameters": [ + { + "name": "test-scorer", + "scorer": { + "type": "command_scorer", + "command": "echo 'score=1.0'", + }, + "weight": 1.0, + } + ], + }, + validation_type="FORWARD", + ) + + def test_run_async(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test run_async method returns ScenarioRun wrapper.""" + mock_client.scenarios.start_run.return_value = scenario_run_view + + scenario = Scenario(mock_client, "scn_123") + run = scenario.run_async(run_name="test-run") + + assert run.id == "run_123" + assert run.devbox_id == "dev_123" + mock_client.scenarios.start_run.assert_called_once_with( + scenario_id="scn_123", + run_name="test-run", + ) + + def test_run(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test run method.""" + mock_client.scenarios.start_run_and_await_env_ready.return_value = scenario_run_view + + scenario = Scenario(mock_client, "scn_123") + run = scenario.run(run_name="test-run") + + assert run.id == "run_123" + assert run.devbox_id == "dev_123" + mock_client.scenarios.start_run_and_await_env_ready.assert_called_once_with( + scenario_id="scn_123", + run_name="test-run", + ) diff --git a/tests/sdk/test_scenario_run.py b/tests/sdk/test_scenario_run.py new file mode 100644 index 000000000..54ea6e89b --- /dev/null +++ b/tests/sdk/test_scenario_run.py @@ -0,0 +1,157 @@ +"""Comprehensive tests for sync ScenarioRun class.""" + +from __future__ import annotations + +from types import SimpleNamespace +from pathlib import Path +from unittest.mock import Mock + +from tests.sdk.conftest import MockDevboxView, MockScenarioRunView +from runloop_api_client.sdk import ScenarioRun + + +class TestScenarioRun: + """Tests for ScenarioRun class.""" + + def test_init(self, mock_client: Mock) -> None: + """Test ScenarioRun initialization.""" + run = ScenarioRun(mock_client, "run_123", "dev_123") + assert run.id == "run_123" + assert run.devbox_id == "dev_123" + + def test_repr(self, mock_client: Mock) -> None: + """Test ScenarioRun string representation.""" + run = ScenarioRun(mock_client, "run_123", "dev_123") + assert repr(run) == "" + + def test_devbox_property(self, mock_client: Mock) -> None: + """Test devbox property returns Devbox wrapper.""" + run = ScenarioRun(mock_client, "run_123", "dev_123") + devbox = run.devbox + + assert devbox.id == "dev_123" + + def test_get_info(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test get_info method.""" + mock_client.scenarios.runs.retrieve.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.get_info() + + assert result == scenario_run_view + mock_client.scenarios.runs.retrieve.assert_called_once_with("run_123") + + def test_await_env_ready( + self, mock_client: Mock, scenario_run_view: MockScenarioRunView, devbox_view: MockDevboxView + ) -> None: + """Test await_env_ready method.""" + mock_client.devboxes.await_running.return_value = devbox_view + mock_client.scenarios.runs.retrieve.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.await_env_ready() + + mock_client.devboxes.await_running.assert_called_once_with("dev_123", polling_config=None) + assert result == scenario_run_view + + def test_score(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test score method.""" + scenario_run_view.state = "scoring" + mock_client.scenarios.runs.score.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.score() + + assert result == scenario_run_view + mock_client.scenarios.runs.score.assert_called_once_with("run_123") + + def test_await_scored(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test await_scored method.""" + scenario_run_view.state = "scored" + mock_client.scenarios.runs.await_scored.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.await_scored() + + assert result == scenario_run_view + mock_client.scenarios.runs.await_scored.assert_called_once_with("run_123") + + def test_score_and_await(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test score_and_await method.""" + scenario_run_view.state = "scored" + mock_client.scenarios.runs.score_and_await.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.score_and_await() + + assert result == scenario_run_view + mock_client.scenarios.runs.score_and_await.assert_called_once_with("run_123") + + def test_score_and_complete(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test score_and_complete method.""" + scenario_run_view.state = "completed" + mock_client.scenarios.runs.score_and_complete.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.score_and_complete() + + assert result == scenario_run_view + mock_client.scenarios.runs.score_and_complete.assert_called_once_with("run_123") + + def test_complete(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test complete method.""" + scenario_run_view.state = "completed" + mock_client.scenarios.runs.complete.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.complete() + + assert result == scenario_run_view + mock_client.scenarios.runs.complete.assert_called_once_with("run_123") + + def test_cancel(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None: + """Test cancel method.""" + scenario_run_view.state = "canceled" + mock_client.scenarios.runs.cancel.return_value = scenario_run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.cancel() + + assert result == scenario_run_view + mock_client.scenarios.runs.cancel.assert_called_once_with("run_123") + + def test_download_logs(self, mock_client: Mock, tmp_path: Path) -> None: + """Test download_logs method writes to file.""" + mock_response = Mock() + mock_response.write_to_file = Mock() + mock_client.scenarios.runs.download_logs.return_value = mock_response + + run = ScenarioRun(mock_client, "run_123", "dev_123") + output_path = tmp_path / "logs.zip" + run.download_logs(output_path) + + mock_client.scenarios.runs.download_logs.assert_called_once_with("run_123") + mock_response.write_to_file.assert_called_once_with(output_path) + + def test_get_score_when_scored(self, mock_client: Mock) -> None: + """Test get_score returns scoring result when scored.""" + scoring_result = SimpleNamespace(score=0.95, scoring_function_results=[]) + run_view = MockScenarioRunView(state="scored", scoring_contract_result=scoring_result) + mock_client.scenarios.runs.retrieve.return_value = run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.get_score() + + assert result == scoring_result + mock_client.scenarios.runs.retrieve.assert_called_once_with("run_123") + + def test_get_score_when_not_scored(self, mock_client: Mock) -> None: + """Test get_score returns None when not scored.""" + run_view = MockScenarioRunView(state="running", scoring_contract_result=None) + mock_client.scenarios.runs.retrieve.return_value = run_view + + run = ScenarioRun(mock_client, "run_123", "dev_123") + result = run.get_score() + + assert result is None + mock_client.scenarios.runs.retrieve.assert_called_once_with("run_123") diff --git a/tests/smoketests/sdk/test_async_agent.py b/tests/smoketests/sdk/test_async_agent.py index 255f54d05..89e72e1e3 100644 --- a/tests/smoketests/sdk/test_async_agent.py +++ b/tests/smoketests/sdk/test_async_agent.py @@ -120,9 +120,15 @@ async def test_list_multiple_agents(self, async_sdk_client: AsyncRunloopSDK) -> } # Create multiple agents - agent1 = await async_sdk_client.agent.create(name=unique_name("sdk-async-agent-test-list-1"), source=source_config) - agent2 = await async_sdk_client.agent.create(name=unique_name("sdk-async-agent-test-list-2"), source=source_config) - agent3 = await async_sdk_client.agent.create(name=unique_name("sdk-async-agent-test-list-3"), source=source_config) + agent1 = await async_sdk_client.agent.create( + name=unique_name("sdk-async-agent-test-list-1"), source=source_config + ) + agent2 = await async_sdk_client.agent.create( + name=unique_name("sdk-async-agent-test-list-2"), source=source_config + ) + agent3 = await async_sdk_client.agent.create( + name=unique_name("sdk-async-agent-test-list-3"), source=source_config + ) try: # List agents diff --git a/tests/smoketests/sdk/test_async_scenario.py b/tests/smoketests/sdk/test_async_scenario.py new file mode 100644 index 000000000..1a6a6a6e7 --- /dev/null +++ b/tests/smoketests/sdk/test_async_scenario.py @@ -0,0 +1,126 @@ +"""Asynchronous SDK smoke tests for Scenario operations.""" + +from __future__ import annotations + +import pytest + +from runloop_api_client.sdk import AsyncRunloopSDK + +pytestmark = [pytest.mark.smoketest] + +TWO_MINUTE_TIMEOUT = 120 +FIVE_MINUTE_TIMEOUT = 300 + + +class TestAsyncScenarioRetrieval: + """Test async scenario retrieval operations.""" + + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + async def test_list_scenarios(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test listing scenarios.""" + scenarios = await async_sdk_client.scenario.list(limit=10) + + assert isinstance(scenarios, list) + # List might be empty, that's okay + assert len(scenarios) >= 0 + + +class TestAsyncScenarioFromId: + """Test async scenario from_id operations.""" + + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + async def test_from_id_get_info(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test from_id followed by get_info on an existing scenario.""" + # First list to get an existing scenario + scenarios = await async_sdk_client.scenario.list(limit=1) + if not scenarios: + pytest.skip("No scenarios available to test from_id") + + scenario_id = scenarios[0].id + + # Get scenario by ID + scenario = async_sdk_client.scenario.from_id(scenario_id) + assert scenario.id == scenario_id + + # Verify we can get info + info = await scenario.get_info() + assert info.id == scenario_id + assert info.name is not None + + +class TestAsyncScenarioRun: + """Test async scenario run operations.""" + + @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT) + async def test_scenario_run_lifecycle(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test running a scenario and accessing the devbox. + + This test: + 1. Lists scenarios to find one to run + 2. Starts a scenario run + 3. Waits for environment to be ready + 4. Accesses the devbox + 5. Cancels the run + """ + # Find a scenario to run + scenarios = await async_sdk_client.scenario.list(limit=5) + if not scenarios: + pytest.skip("No scenarios available to test run") + + # Pick the first scenario + scenario = scenarios[0] + + # Start a run + run = await scenario.run_async(run_name="sdk-smoketest-async-run") + + try: + assert run.id is not None + assert run.devbox_id is not None + + # Wait for environment to be ready + await run.await_env_ready() + + # Access devbox + devbox = run.devbox + assert devbox.id == run.devbox_id + + # Get run info + info = await run.get_info() + assert info.id == run.id + assert info.state in ["running", "scoring", "scored", "completed"] + + finally: + # Clean up - cancel the run + try: + await run.cancel() + except Exception: + pass # Best effort cleanup + + @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT) + async def test_scenario_run_and_await_env_ready(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test run_and_await_env_ready convenience method.""" + # Find a scenario to run + scenarios = await async_sdk_client.scenario.list(limit=5) + if not scenarios: + pytest.skip("No scenarios available to test run") + + scenario = scenarios[0] + + # Start a run and wait for environment in one call + run = await scenario.run(run_name="sdk-smoketest-async-await") + + try: + assert run.id is not None + assert run.devbox_id is not None + + # Devbox should be ready + devbox = run.devbox + info = await devbox.get_info() + assert info.status == "running" + + finally: + # Clean up + try: + await run.cancel() + except Exception: + pass diff --git a/tests/smoketests/sdk/test_scenario.py b/tests/smoketests/sdk/test_scenario.py new file mode 100644 index 000000000..af8d81486 --- /dev/null +++ b/tests/smoketests/sdk/test_scenario.py @@ -0,0 +1,126 @@ +"""Synchronous SDK smoke tests for Scenario operations.""" + +from __future__ import annotations + +import pytest + +from runloop_api_client.sdk import RunloopSDK + +pytestmark = [pytest.mark.smoketest] + +TWO_MINUTE_TIMEOUT = 120 +FIVE_MINUTE_TIMEOUT = 300 + + +class TestScenarioRetrieval: + """Test scenario retrieval operations.""" + + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + def test_list_scenarios(self, sdk_client: RunloopSDK) -> None: + """Test listing scenarios.""" + scenarios = sdk_client.scenario.list(limit=10) + + assert isinstance(scenarios, list) + # List might be empty, that's okay + assert len(scenarios) >= 0 + + +class TestScenarioFromId: + """Test scenario from_id operations.""" + + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + def test_from_id_get_info(self, sdk_client: RunloopSDK) -> None: + """Test from_id followed by get_info on an existing scenario.""" + # First list to get an existing scenario + scenarios = sdk_client.scenario.list(limit=1) + if not scenarios: + pytest.skip("No scenarios available to test from_id") + + scenario_id = scenarios[0].id + + # Get scenario by ID + scenario = sdk_client.scenario.from_id(scenario_id) + assert scenario.id == scenario_id + + # Verify we can get info + info = scenario.get_info() + assert info.id == scenario_id + assert info.name is not None + + +class TestScenarioRun: + """Test scenario run operations.""" + + @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT) + def test_scenario_run_lifecycle(self, sdk_client: RunloopSDK) -> None: + """Test running a scenario and accessing the devbox. + + This test: + 1. Lists scenarios to find one to run + 2. Starts a scenario run + 3. Waits for environment to be ready + 4. Accesses the devbox + 5. Cancels the run + """ + # Find a scenario to run + scenarios = sdk_client.scenario.list(limit=5) + if not scenarios: + pytest.skip("No scenarios available to test run") + + # Pick the first scenario + scenario = scenarios[0] + + # Start a run + run = scenario.run_async(run_name="sdk-smoketest-run") + + try: + assert run.id is not None + assert run.devbox_id is not None + + # Wait for environment to be ready + run.await_env_ready() + + # Access devbox + devbox = run.devbox + assert devbox.id == run.devbox_id + + # Get run info + info = run.get_info() + assert info.id == run.id + assert info.state in ["running", "scoring", "scored", "completed"] + + finally: + # Clean up - cancel the run + try: + run.cancel() + except Exception: + pass # Best effort cleanup + + @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT) + def test_scenario_run_and_await_env_ready(self, sdk_client: RunloopSDK) -> None: + """Test run_and_await_env_ready convenience method.""" + # Find a scenario to run + scenarios = sdk_client.scenario.list(limit=5) + if not scenarios: + pytest.skip("No scenarios available to test run") + + scenario = scenarios[0] + + # Start a run and wait for environment in one call + run = scenario.run(run_name="sdk-smoketest-await") + + try: + assert run.id is not None + assert run.devbox_id is not None + + # Devbox should be ready + devbox = run.devbox + info = devbox.get_info() + assert info.status == "running" + + finally: + # Clean up + try: + run.cancel() + except Exception: + pass diff --git a/uv.lock b/uv.lock index 50fa85fae..ff0202dfd 100644 --- a/uv.lock +++ b/uv.lock @@ -207,17 +207,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] @@ -301,15 +300,15 @@ wheels = [ [[package]] name = "beautifulsoup4" -version = "4.14.2" +version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] @@ -774,14 +773,14 @@ wheels = [ [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { 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" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] @@ -1105,6 +1104,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "librt" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/c3/cdff3c10e2e608490dc0a310ccf11ba777b3943ad4fcead2a2ade98c21e1/librt-0.6.3.tar.gz", hash = "sha256:c724a884e642aa2bbad52bb0203ea40406ad742368a5f90da1b220e970384aae", size = 54209, upload-time = "2025-11-29T14:01:56.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/84/859df8db21dedab2538ddfbe1d486dda3eb66a98c6ad7ba754a99e25e45e/librt-0.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45660d26569cc22ed30adf583389d8a0d1b468f8b5e518fcf9bfe2cd298f9dd1", size = 27294, upload-time = "2025-11-29T14:00:35.053Z" }, + { url = "https://files.pythonhosted.org/packages/f7/01/ec3971cf9c4f827f17de6729bdfdbf01a67493147334f4ef8fac68936e3a/librt-0.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:54f3b2177fb892d47f8016f1087d21654b44f7fc4cf6571c1c6b3ea531ab0fcf", size = 27635, upload-time = "2025-11-29T14:00:36.496Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f9/3efe201df84dd26388d2e0afa4c4dc668c8e406a3da7b7319152faf835a1/librt-0.6.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c5b31bed2c2f2fa1fcb4815b75f931121ae210dc89a3d607fb1725f5907f1437", size = 81768, upload-time = "2025-11-29T14:00:37.451Z" }, + { url = "https://files.pythonhosted.org/packages/0a/13/f63e60bc219b17f3d8f3d13423cd4972e597b0321c51cac7bfbdd5e1f7b9/librt-0.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f8ed5053ef9fb08d34f1fd80ff093ccbd1f67f147633a84cf4a7d9b09c0f089", size = 85884, upload-time = "2025-11-29T14:00:38.433Z" }, + { url = "https://files.pythonhosted.org/packages/c2/42/0068f14f39a79d1ce8a19d4988dd07371df1d0a7d3395fbdc8a25b1c9437/librt-0.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f0e4bd9bcb0ee34fa3dbedb05570da50b285f49e52c07a241da967840432513", size = 85830, upload-time = "2025-11-29T14:00:39.418Z" }, + { url = "https://files.pythonhosted.org/packages/14/1c/87f5af3a9e6564f09e50c72f82fc3057fd42d1facc8b510a707d0438c4ad/librt-0.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8f89c8d20dfa648a3f0a56861946eb00e5b00d6b00eea14bc5532b2fcfa8ef1", size = 88086, upload-time = "2025-11-29T14:00:40.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/22153b98b88a913b5b3f266f12e57df50a2a6960b3f8fcb825b1a0cfe40a/librt-0.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecc2c526547eacd20cb9fbba19a5268611dbc70c346499656d6cf30fae328977", size = 86470, upload-time = "2025-11-29T14:00:41.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/3c/ea1edb587799b1edcc22444e0630fa422e32d7aaa5bfb5115b948acc2d1c/librt-0.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fbedeb9b48614d662822ee514567d2d49a8012037fc7b4cd63f282642c2f4b7d", size = 89079, upload-time = "2025-11-29T14:00:42.882Z" }, + { url = "https://files.pythonhosted.org/packages/73/ad/50bb4ae6b07c9f3ab19653e0830a210533b30eb9a18d515efb5a2b9d0c7c/librt-0.6.3-cp310-cp310-win32.whl", hash = "sha256:0765b0fe0927d189ee14b087cd595ae636bef04992e03fe6dfdaa383866c8a46", size = 19820, upload-time = "2025-11-29T14:00:44.211Z" }, + { url = "https://files.pythonhosted.org/packages/7a/12/7426ee78f3b1dbe11a90619d54cb241ca924ca3c0ff9ade3992178e9b440/librt-0.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:8c659f9fb8a2f16dc4131b803fa0144c1dadcb3ab24bb7914d01a6da58ae2457", size = 21332, upload-time = "2025-11-29T14:00:45.427Z" }, + { url = "https://files.pythonhosted.org/packages/8b/80/bc60fd16fe24910bf5974fb914778a2e8540cef55385ab2cb04a0dfe42c4/librt-0.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:61348cc488b18d1b1ff9f3e5fcd5ac43ed22d3e13e862489d2267c2337285c08", size = 27285, upload-time = "2025-11-29T14:00:46.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/26335536ed9ba097c79cffcee148393592e55758fe76d99015af3e47a6d0/librt-0.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64645b757d617ad5f98c08e07620bc488d4bced9ced91c6279cec418f16056fa", size = 27629, upload-time = "2025-11-29T14:00:47.863Z" }, + { url = "https://files.pythonhosted.org/packages/af/fd/2dcedeacfedee5d2eda23e7a49c1c12ce6221b5d58a13555f053203faafc/librt-0.6.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:26b8026393920320bb9a811b691d73c5981385d537ffc5b6e22e53f7b65d4122", size = 82039, upload-time = "2025-11-29T14:00:49.131Z" }, + { url = "https://files.pythonhosted.org/packages/48/ff/6aa11914b83b0dc2d489f7636942a8e3322650d0dba840db9a1b455f3caa/librt-0.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d998b432ed9ffccc49b820e913c8f327a82026349e9c34fa3690116f6b70770f", size = 86560, upload-time = "2025-11-29T14:00:50.403Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/d25af61958c2c7eb978164aeba0350719f615179ba3f428b682b9a5fdace/librt-0.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e18875e17ef69ba7dfa9623f2f95f3eda6f70b536079ee6d5763ecdfe6cc9040", size = 86494, upload-time = "2025-11-29T14:00:51.383Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/40e75d3b258c801908e64b39788f9491635f9554f8717430a491385bd6f2/librt-0.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a218f85081fc3f70cddaed694323a1ad7db5ca028c379c214e3a7c11c0850523", size = 88914, upload-time = "2025-11-29T14:00:52.688Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/0070c81aba8a169224301c75fb5fb6c3c25ca67e6ced086584fc130d5a67/librt-0.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ef42ff4edd369e84433ce9b188a64df0837f4f69e3d34d3b34d4955c599d03f", size = 86944, upload-time = "2025-11-29T14:00:53.768Z" }, + { url = "https://files.pythonhosted.org/packages/a6/94/809f38887941b7726692e0b5a083dbdc87dbb8cf893e3b286550c5f0b129/librt-0.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e0f2b79993fec23a685b3e8107ba5f8675eeae286675a216da0b09574fa1e47", size = 89852, upload-time = "2025-11-29T14:00:54.71Z" }, + { url = "https://files.pythonhosted.org/packages/58/a3/b0e5b1cda675b91f1111d8ba941da455d8bfaa22f4d2d8963ba96ccb5b12/librt-0.6.3-cp311-cp311-win32.whl", hash = "sha256:fd98cacf4e0fabcd4005c452cb8a31750258a85cab9a59fb3559e8078da408d7", size = 19948, upload-time = "2025-11-29T14:00:55.989Z" }, + { url = "https://files.pythonhosted.org/packages/cc/73/70011c2b37e3be3ece3affd3abc8ebe5cda482b03fd6b3397906321a901e/librt-0.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:e17b5b42c8045867ca9d1f54af00cc2275198d38de18545edaa7833d7e9e4ac8", size = 21406, upload-time = "2025-11-29T14:00:56.874Z" }, + { url = "https://files.pythonhosted.org/packages/91/ee/119aa759290af6ca0729edf513ca390c1afbeae60f3ecae9b9d56f25a8a9/librt-0.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:87597e3d57ec0120a3e1d857a708f80c02c42ea6b00227c728efbc860f067c45", size = 20875, upload-time = "2025-11-29T14:00:57.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2c/b59249c566f98fe90e178baf59e83f628d6c38fb8bc78319301fccda0b5e/librt-0.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74418f718083009108dc9a42c21bf2e4802d49638a1249e13677585fcc9ca176", size = 27841, upload-time = "2025-11-29T14:00:58.925Z" }, + { url = "https://files.pythonhosted.org/packages/40/e8/9db01cafcd1a2872b76114c858f81cc29ce7ad606bc102020d6dabf470fb/librt-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:514f3f363d1ebc423357d36222c37e5c8e6674b6eae8d7195ac9a64903722057", size = 27844, upload-time = "2025-11-29T14:01:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/59/4d/da449d3a7d83cc853af539dee42adc37b755d7eea4ad3880bacfd84b651d/librt-0.6.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cf1115207a5049d1f4b7b4b72de0e52f228d6c696803d94843907111cbf80610", size = 84091, upload-time = "2025-11-29T14:01:01.118Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6c/f90306906fb6cc6eaf4725870f0347115de05431e1f96d35114392d31fda/librt-0.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad8ba80cdcea04bea7b78fcd4925bfbf408961e9d8397d2ee5d3ec121e20c08c", size = 88239, upload-time = "2025-11-29T14:01:02.11Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ae/473ce7b423cfac2cb503851a89d9d2195bf615f534d5912bf86feeebbee7/librt-0.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4018904c83eab49c814e2494b4e22501a93cdb6c9f9425533fe693c3117126f9", size = 88815, upload-time = "2025-11-29T14:01:03.114Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6d/934df738c87fb9617cabefe4891eece585a06abe6def25b4bca3b174429d/librt-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8983c5c06ac9c990eac5eb97a9f03fe41dc7e9d7993df74d9e8682a1056f596c", size = 90598, upload-time = "2025-11-29T14:01:04.071Z" }, + { url = "https://files.pythonhosted.org/packages/72/89/eeaa124f5e0f431c2b39119550378ae817a4b1a3c93fd7122f0639336fff/librt-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7769c579663a6f8dbf34878969ac71befa42067ce6bf78e6370bf0d1194997c", size = 88603, upload-time = "2025-11-29T14:01:05.02Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ed/c60b3c1cfc27d709bc0288af428ce58543fcb5053cf3eadbc773c24257f5/librt-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d3c9a07eafdc70556f8c220da4a538e715668c0c63cabcc436a026e4e89950bf", size = 92112, upload-time = "2025-11-29T14:01:06.304Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ab/f56169be5f716ef4ab0277be70bcb1874b4effc262e655d85b505af4884d/librt-0.6.3-cp312-cp312-win32.whl", hash = "sha256:38320386a48a15033da295df276aea93a92dfa94a862e06893f75ea1d8bbe89d", size = 20127, upload-time = "2025-11-29T14:01:07.283Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/222750ce82bf95125529eaab585ac7e2829df252f3cfc05d68792fb1dd2c/librt-0.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:c0ecf4786ad0404b072196b5df774b1bb23c8aacdcacb6c10b4128bc7b00bd01", size = 21545, upload-time = "2025-11-29T14:01:08.184Z" }, + { url = "https://files.pythonhosted.org/packages/72/c9/f731ddcfb72f446a92a8674c6b8e1e2242773cce43a04f41549bd8b958ff/librt-0.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:9f2a6623057989ebc469cd9cc8fe436c40117a0147627568d03f84aef7854c55", size = 20946, upload-time = "2025-11-29T14:01:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/3055dd440f8b8b3b7e8624539a0749dd8e1913e978993bcca9ce7e306231/librt-0.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e716f9012148a81f02f46a04fc4c663420c6fbfeacfac0b5e128cf43b4413d3", size = 27874, upload-time = "2025-11-29T14:01:10.615Z" }, + { url = "https://files.pythonhosted.org/packages/ef/93/226d7dd455eaa4c26712b5ccb2dfcca12831baa7f898c8ffd3a831e29fda/librt-0.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:669ff2495728009a96339c5ad2612569c6d8be4474e68f3f3ac85d7c3261f5f5", size = 27852, upload-time = "2025-11-29T14:01:11.535Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8b/db9d51191aef4e4cc06285250affe0bb0ad8b2ed815f7ca77951655e6f02/librt-0.6.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:349b6873ebccfc24c9efd244e49da9f8a5c10f60f07575e248921aae2123fc42", size = 84264, upload-time = "2025-11-29T14:01:12.461Z" }, + { url = "https://files.pythonhosted.org/packages/8d/53/297c96bda3b5a73bdaf748f1e3ae757edd29a0a41a956b9c10379f193417/librt-0.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c74c26736008481c9f6d0adf1aedb5a52aff7361fea98276d1f965c0256ee70", size = 88432, upload-time = "2025-11-29T14:01:13.405Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/c005516071123278e340f22de72fa53d51e259d49215295c212da16c4dc2/librt-0.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:408a36ddc75e91918cb15b03460bdc8a015885025d67e68c6f78f08c3a88f522", size = 89014, upload-time = "2025-11-29T14:01:14.373Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9b/ea715f818d926d17b94c80a12d81a79e95c44f52848e61e8ca1ff29bb9a9/librt-0.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e61ab234624c9ffca0248a707feffe6fac2343758a36725d8eb8a6efef0f8c30", size = 90807, upload-time = "2025-11-29T14:01:15.377Z" }, + { url = "https://files.pythonhosted.org/packages/f0/fc/4e2e4c87e002fa60917a8e474fd13c4bac9a759df82be3778573bb1ab954/librt-0.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:324462fe7e3896d592b967196512491ec60ca6e49c446fe59f40743d08c97917", size = 88890, upload-time = "2025-11-29T14:01:16.633Z" }, + { url = "https://files.pythonhosted.org/packages/70/7f/c7428734fbdfd4db3d5b9237fc3a857880b2ace66492836f6529fef25d92/librt-0.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36b2ec8c15030002c7f688b4863e7be42820d7c62d9c6eece3db54a2400f0530", size = 92300, upload-time = "2025-11-29T14:01:17.658Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0c/738c4824fdfe74dc0f95d5e90ef9e759d4ecf7fd5ba964d54a7703322251/librt-0.6.3-cp313-cp313-win32.whl", hash = "sha256:25b1b60cb059471c0c0c803e07d0dfdc79e41a0a122f288b819219ed162672a3", size = 20159, upload-time = "2025-11-29T14:01:18.61Z" }, + { url = "https://files.pythonhosted.org/packages/f2/95/93d0e61bc617306ecf4c54636b5cbde4947d872563565c4abdd9d07a39d3/librt-0.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:10a95ad074e2a98c9e4abc7f5b7d40e5ecbfa84c04c6ab8a70fabf59bd429b88", size = 21484, upload-time = "2025-11-29T14:01:19.506Z" }, + { url = "https://files.pythonhosted.org/packages/10/23/abd7ace79ab54d1dbee265f13529266f686a7ce2d21ab59a992f989009b6/librt-0.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:17000df14f552e86877d67e4ab7966912224efc9368e998c96a6974a8d609bf9", size = 20935, upload-time = "2025-11-29T14:01:20.415Z" }, + { url = "https://files.pythonhosted.org/packages/83/14/c06cb31152182798ed98be73f54932ab984894f5a8fccf9b73130897a938/librt-0.6.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8e695f25d1a425ad7a272902af8ab8c8d66c1998b177e4b5f5e7b4e215d0c88a", size = 27566, upload-time = "2025-11-29T14:01:21.609Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/ce83ca7b057b06150519152f53a0b302d7c33c8692ce2f01f669b5a819d9/librt-0.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3e84a4121a7ae360ca4da436548a9c1ca8ca134a5ced76c893cc5944426164bd", size = 27753, upload-time = "2025-11-29T14:01:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ec/739a885ef0a2839b6c25f1b01c99149d2cb6a34e933ffc8c051fcd22012e/librt-0.6.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:05f385a414de3f950886ea0aad8f109650d4b712cf9cc14cc17f5f62a9ab240b", size = 83178, upload-time = "2025-11-29T14:01:23.555Z" }, + { url = "https://files.pythonhosted.org/packages/db/bd/dc18bb1489d48c0911b9f4d72eae2d304ea264e215ba80f1e6ba4a9fc41d/librt-0.6.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36a8e337461150b05ca2c7bdedb9e591dfc262c5230422cea398e89d0c746cdc", size = 87266, upload-time = "2025-11-29T14:01:24.532Z" }, + { url = "https://files.pythonhosted.org/packages/94/f3/d0c5431b39eef15e48088b2d739ad84b17c2f1a22c0345c6d4c4a42b135e/librt-0.6.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcbe48f6a03979384f27086484dc2a14959be1613cb173458bd58f714f2c48f3", size = 87623, upload-time = "2025-11-29T14:01:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/3b/15/9a52e90834e4bd6ee16cdbaf551cb32227cbaad27398391a189c489318bc/librt-0.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4bca9e4c260233fba37b15c4ec2f78aa99c1a79fbf902d19dd4a763c5c3fb751", size = 89436, upload-time = "2025-11-29T14:01:26.769Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8a/a7e78e46e8486e023c50f21758930ef4793999115229afd65de69e94c9cc/librt-0.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:760c25ed6ac968e24803eb5f7deb17ce026902d39865e83036bacbf5cf242aa8", size = 87540, upload-time = "2025-11-29T14:01:27.756Z" }, + { url = "https://files.pythonhosted.org/packages/49/01/93799044a1cccac31f1074b07c583e181829d240539657e7f305ae63ae2a/librt-0.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4a93a353ccff20df6e34fa855ae8fd788832c88f40a9070e3ddd3356a9f0e", size = 90597, upload-time = "2025-11-29T14:01:29.35Z" }, + { url = "https://files.pythonhosted.org/packages/a7/29/00c7f58b8f8eb1bad6529ffb6c9cdcc0890a27dac59ecda04f817ead5277/librt-0.6.3-cp314-cp314-win32.whl", hash = "sha256:cb92741c2b4ea63c09609b064b26f7f5d9032b61ae222558c55832ec3ad0bcaf", size = 18955, upload-time = "2025-11-29T14:01:30.325Z" }, + { url = "https://files.pythonhosted.org/packages/d7/13/2739e6e197a9f751375a37908a6a5b0bff637b81338497a1bcb5817394da/librt-0.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:fdcd095b1b812d756fa5452aca93b962cf620694c0cadb192cec2bb77dcca9a2", size = 20263, upload-time = "2025-11-29T14:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/e1/73/393868fc2158705ea003114a24e73bb10b03bda31e9ad7b5c5ec6575338b/librt-0.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:822ca79e28720a76a935c228d37da6579edef048a17cd98d406a2484d10eda78", size = 19575, upload-time = "2025-11-29T14:01:32.229Z" }, + { url = "https://files.pythonhosted.org/packages/48/6d/3c8ff3dec21bf804a205286dd63fd28dcdbe00b8dd7eb7ccf2e21a40a0b0/librt-0.6.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:078cd77064d1640cb7b0650871a772956066174d92c8aeda188a489b58495179", size = 28732, upload-time = "2025-11-29T14:01:33.165Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/e214b8b4aa34ed3d3f1040719c06c4d22472c40c5ef81a922d5af7876eb4/librt-0.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5cc22f7f5c0cc50ed69f4b15b9c51d602aabc4500b433aaa2ddd29e578f452f7", size = 29065, upload-time = "2025-11-29T14:01:34.088Z" }, + { url = "https://files.pythonhosted.org/packages/ab/90/ef61ed51f0a7770cc703422d907a757bbd8811ce820c333d3db2fd13542a/librt-0.6.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:14b345eb7afb61b9fdcdfda6738946bd11b8e0f6be258666b0646af3b9bb5916", size = 93703, upload-time = "2025-11-29T14:01:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ae/c30bb119c35962cbe9a908a71da99c168056fc3f6e9bbcbc157d0b724d89/librt-0.6.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d46aa46aa29b067f0b8b84f448fd9719aaf5f4c621cc279164d76a9dc9ab3e8", size = 98890, upload-time = "2025-11-29T14:01:36.031Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/47a4a78d252d36f072b79d592df10600d379a895c3880c8cbd2ac699f0ad/librt-0.6.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b51ba7d9d5d9001494769eca8c0988adce25d0a970c3ba3f2eb9df9d08036fc", size = 98255, upload-time = "2025-11-29T14:01:37.058Z" }, + { url = "https://files.pythonhosted.org/packages/e5/28/779b5cc3cd9987683884eb5f5672e3251676bebaaae6b7da1cf366eb1da1/librt-0.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ced0925a18fddcff289ef54386b2fc230c5af3c83b11558571124bfc485b8c07", size = 100769, upload-time = "2025-11-29T14:01:38.413Z" }, + { url = "https://files.pythonhosted.org/packages/28/d7/771755e57c375cb9d25a4e106f570607fd856e2cb91b02418db1db954796/librt-0.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6bac97e51f66da2ca012adddbe9fd656b17f7368d439de30898f24b39512f40f", size = 98580, upload-time = "2025-11-29T14:01:39.459Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ec/8b157eb8fbc066339a2f34b0aceb2028097d0ed6150a52e23284a311eafe/librt-0.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b2922a0e8fa97395553c304edc3bd36168d8eeec26b92478e292e5d4445c1ef0", size = 101706, upload-time = "2025-11-29T14:01:40.474Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/4aaead9a06c795a318282aebf7d3e3e578fa889ff396e1b640c3be4c7806/librt-0.6.3-cp314-cp314t-win32.whl", hash = "sha256:f33462b19503ba68d80dac8a1354402675849259fb3ebf53b67de86421735a3a", size = 19465, upload-time = "2025-11-29T14:01:41.77Z" }, + { url = "https://files.pythonhosted.org/packages/3a/61/b7e6a02746c1731670c19ba07d86da90b1ae45d29e405c0b5615abf97cde/librt-0.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:04f8ce401d4f6380cfc42af0f4e67342bf34c820dae01343f58f472dbac75dcf", size = 21042, upload-time = "2025-11-29T14:01:42.865Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3d/72cc9ec90bb80b5b1a65f0bb74a0f540195837baaf3b98c7fa4a7aa9718e/librt-0.6.3-cp314-cp314t-win_arm64.whl", hash = "sha256:afb39550205cc5e5c935762c6bf6a2bb34f7d21a68eadb25e2db7bf3593fecc0", size = 20246, upload-time = "2025-11-29T14:01:44.13Z" }, + { url = "https://files.pythonhosted.org/packages/d0/85/63b34f02f56b86574bb6d1ed29415346332a1edea2cd12b29fb0863456cb/librt-0.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09262cb2445b6f15d09141af20b95bb7030c6f13b00e876ad8fdd1a9045d6aa5", size = 27438, upload-time = "2025-11-29T14:01:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/57/cc/d2952a97b5e19c0a88f04b161d1c4b8336ad093a8fecd49801258b3cc816/librt-0.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57705e8eec76c5b77130d729c0f70190a9773366c555c5457c51eace80afd873", size = 27783, upload-time = "2025-11-29T14:01:46.398Z" }, + { url = "https://files.pythonhosted.org/packages/09/38/007124092f9345a273b27b070e894afa66a737b576f1f7c37354dd4ffe24/librt-0.6.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3ac2a7835434b31def8ed5355dd9b895bbf41642d61967522646d1d8b9681106", size = 81403, upload-time = "2025-11-29T14:01:47.659Z" }, + { url = "https://files.pythonhosted.org/packages/89/ca/079722b2b518bc6c38e3ba7ab07f2f0e5c6d4905d7f34ee138f862a69853/librt-0.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71f0a5918aebbea1e7db2179a8fe87e8a8732340d9e8b8107401fb407eda446e", size = 85678, upload-time = "2025-11-29T14:01:48.638Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7a/dcba5bd2f71d3815ea3886929f218c259665b9f2f7115163eed2ccb50599/librt-0.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa346e202e6e1ebc01fe1c69509cffe486425884b96cb9ce155c99da1ecbe0e9", size = 85655, upload-time = "2025-11-29T14:01:49.894Z" }, + { url = "https://files.pythonhosted.org/packages/7a/47/5bfa3816b58e6105d1ea1a165af73ae6560b4acb80ce3304793ac9a36ec1/librt-0.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:92267f865c7bbd12327a0d394666948b9bf4b51308b52947c0cc453bfa812f5d", size = 88093, upload-time = "2025-11-29T14:01:50.902Z" }, + { url = "https://files.pythonhosted.org/packages/76/33/ac5c01cfc68b208423a00451640fad6744f974a9c9e8a62d89e9a5e47159/librt-0.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:86605d5bac340beb030cbc35859325982a79047ebdfba1e553719c7126a2389d", size = 86386, upload-time = "2025-11-29T14:01:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4d/9460e0b6d53cabeb78016e7e4e7d70dcd11fe90ef37c704856bd8a2ff533/librt-0.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:98e4bbecbef8d2a60ecf731d735602feee5ac0b32117dbbc765e28b054bac912", size = 89064, upload-time = "2025-11-29T14:01:53.249Z" }, + { url = "https://files.pythonhosted.org/packages/20/f3/cb8410cbd34718fe7e11b006bacc8093d3f758c7d771c95613caebe7a9fc/librt-0.6.3-cp39-cp39-win32.whl", hash = "sha256:3caa0634c02d5ff0b2ae4a28052e0d8c5f20d497623dc13f629bd4a9e2a6efad", size = 19867, upload-time = "2025-11-29T14:01:54.201Z" }, + { url = "https://files.pythonhosted.org/packages/14/7e/521c046b3bc9316c408d159bc4f2c4be607280b3646416b953bdd4efda6f/librt-0.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:b47395091e7e0ece1e6ebac9b98bf0c9084d1e3d3b2739aa566be7e56e3f7bf2", size = 21408, upload-time = "2025-11-29T14:01:55.126Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1478,53 +1560,54 @@ wheels = [ [[package]] name = "mypy" -version = "1.18.2" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -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" }, +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" }, + { url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, + { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, + { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, + { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, + { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/b4/59/a7748ef43446163a93159d82bb270c6c4f3d94c1fcbdd2a29a7e439e74d7/mypy-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0dde5cb375cb94deff0d4b548b993bec52859d1651e073d63a1386d392a95495", size = 13094255, upload-time = "2025-11-28T15:47:14.282Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0b/92ebf5abc83f559a35dcba3bd9227726b04b04178f1e521f38e647b930eb/mypy-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1cf9c59398db1c68a134b0b5354a09a1e124523f00bacd68e553b8bd16ff3299", size = 12161414, upload-time = "2025-11-28T15:45:03.302Z" }, + { url = "https://files.pythonhosted.org/packages/aa/03/19412f0a786722055a52c01b4c5d71e5b5443a89f6bbcdd445408240e217/mypy-1.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3210d87b30e6af9c8faed61be2642fcbe60ef77cec64fa1ef810a630a4cf671c", size = 12756782, upload-time = "2025-11-28T15:46:49.522Z" }, + { url = "https://files.pythonhosted.org/packages/cb/85/395d53c9098b251414b0448cdadcd3277523ff36f5abda6d26ff945dbdb3/mypy-1.19.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2c1101ab41d01303103ab6ef82cbbfedb81c1a060c868fa7cc013d573d37ab5", size = 13503492, upload-time = "2025-11-28T15:48:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/dd/33/1ab1113e3778617ae7aba66b4b537f90512bd279ff65b6c984fb91fbb2d3/mypy-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ea4fd21bb48f0da49e6d3b37ef6bd7e8228b9fe41bbf4d80d9364d11adbd43c", size = 13787703, upload-time = "2025-11-28T15:48:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2d/8b0821b3e0d538de1ad96c86502256c7326274d5cb74e0b373efaada273f/mypy-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:16f76ff3f3fd8137aadf593cb4607d82634fca675e8211ad75c43d86033ee6c6", size = 10049225, upload-time = "2025-11-28T15:45:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, ] [[package]] @@ -2072,28 +2155,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/f0/62b5a1a723fe183650109407fa56abb433b00aa1c0b9ba555f9c4efec2c6/ruff-0.14.6.tar.gz", hash = "sha256:6f0c742ca6a7783a736b867a263b9a7a80a45ce9bee391eeda296895f1b4e1cc", size = 5669501, upload-time = "2025-11-21T14:26:17.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/d2/7dd544116d107fffb24a0064d41a5d2ed1c9d6372d142f9ba108c8e39207/ruff-0.14.6-py3-none-linux_armv6l.whl", hash = "sha256:d724ac2f1c240dbd01a2ae98db5d1d9a5e1d9e96eba999d1c48e30062df578a3", size = 13326119, upload-time = "2025-11-21T14:25:24.2Z" }, - { url = "https://files.pythonhosted.org/packages/36/6a/ad66d0a3315d6327ed6b01f759d83df3c4d5f86c30462121024361137b6a/ruff-0.14.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9f7539ea257aa4d07b7ce87aed580e485c40143f2473ff2f2b75aee003186004", size = 13526007, upload-time = "2025-11-21T14:25:26.906Z" }, - { url = "https://files.pythonhosted.org/packages/a3/9d/dae6db96df28e0a15dea8e986ee393af70fc97fd57669808728080529c37/ruff-0.14.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7f6007e55b90a2a7e93083ba48a9f23c3158c433591c33ee2e99a49b889c6332", size = 12676572, upload-time = "2025-11-21T14:25:29.826Z" }, - { url = "https://files.pythonhosted.org/packages/76/a4/f319e87759949062cfee1b26245048e92e2acce900ad3a909285f9db1859/ruff-0.14.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a8e7b9d73d8728b68f632aa8e824ef041d068d231d8dbc7808532d3629a6bef", size = 13140745, upload-time = "2025-11-21T14:25:32.788Z" }, - { url = "https://files.pythonhosted.org/packages/95/d3/248c1efc71a0a8ed4e8e10b4b2266845d7dfc7a0ab64354afe049eaa1310/ruff-0.14.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d50d45d4553a3ebcbd33e7c5e0fe6ca4aafd9a9122492de357205c2c48f00775", size = 13076486, upload-time = "2025-11-21T14:25:35.601Z" }, - { url = "https://files.pythonhosted.org/packages/a5/19/b68d4563fe50eba4b8c92aa842149bb56dd24d198389c0ed12e7faff4f7d/ruff-0.14.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:118548dd121f8a21bfa8ab2c5b80e5b4aed67ead4b7567790962554f38e598ce", size = 13727563, upload-time = "2025-11-21T14:25:38.514Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/943169436832d4b0e867235abbdb57ce3a82367b47e0280fa7b4eabb7593/ruff-0.14.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:57256efafbfefcb8748df9d1d766062f62b20150691021f8ab79e2d919f7c11f", size = 15199755, upload-time = "2025-11-21T14:25:41.516Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b9/288bb2399860a36d4bb0541cb66cce3c0f4156aaff009dc8499be0c24bf2/ruff-0.14.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff18134841e5c68f8e5df1999a64429a02d5549036b394fafbe410f886e1989d", size = 14850608, upload-time = "2025-11-21T14:25:44.428Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b1/a0d549dd4364e240f37e7d2907e97ee80587480d98c7799d2d8dc7a2f605/ruff-0.14.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c4b7ec1e66a105d5c27bd57fa93203637d66a26d10ca9809dc7fc18ec58440", size = 14118754, upload-time = "2025-11-21T14:25:47.214Z" }, - { url = "https://files.pythonhosted.org/packages/13/ac/9b9fe63716af8bdfddfacd0882bc1586f29985d3b988b3c62ddce2e202c3/ruff-0.14.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167843a6f78680746d7e226f255d920aeed5e4ad9c03258094a2d49d3028b105", size = 13949214, upload-time = "2025-11-21T14:25:50.002Z" }, - { url = "https://files.pythonhosted.org/packages/12/27/4dad6c6a77fede9560b7df6802b1b697e97e49ceabe1f12baf3ea20862e9/ruff-0.14.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:16a33af621c9c523b1ae006b1b99b159bf5ac7e4b1f20b85b2572455018e0821", size = 14106112, upload-time = "2025-11-21T14:25:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/6a/db/23e322d7177873eaedea59a7932ca5084ec5b7e20cb30f341ab594130a71/ruff-0.14.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1432ab6e1ae2dc565a7eea707d3b03a0c234ef401482a6f1621bc1f427c2ff55", size = 13035010, upload-time = "2025-11-21T14:25:55.536Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9c/20e21d4d69dbb35e6a1df7691e02f363423658a20a2afacf2a2c011800dc/ruff-0.14.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c55cfbbe7abb61eb914bfd20683d14cdfb38a6d56c6c66efa55ec6570ee4e71", size = 13054082, upload-time = "2025-11-21T14:25:58.625Z" }, - { url = "https://files.pythonhosted.org/packages/66/25/906ee6a0464c3125c8d673c589771a974965c2be1a1e28b5c3b96cb6ef88/ruff-0.14.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:efea3c0f21901a685fff4befda6d61a1bf4cb43de16da87e8226a281d614350b", size = 13303354, upload-time = "2025-11-21T14:26:01.816Z" }, - { url = "https://files.pythonhosted.org/packages/4c/58/60577569e198d56922b7ead07b465f559002b7b11d53f40937e95067ca1c/ruff-0.14.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:344d97172576d75dc6afc0e9243376dbe1668559c72de1864439c4fc95f78185", size = 14054487, upload-time = "2025-11-21T14:26:05.058Z" }, - { url = "https://files.pythonhosted.org/packages/67/0b/8e4e0639e4cc12547f41cb771b0b44ec8225b6b6a93393176d75fe6f7d40/ruff-0.14.6-py3-none-win32.whl", hash = "sha256:00169c0c8b85396516fdd9ce3446c7ca20c2a8f90a77aa945ba6b8f2bfe99e85", size = 13013361, upload-time = "2025-11-21T14:26:08.152Z" }, - { url = "https://files.pythonhosted.org/packages/fb/02/82240553b77fd1341f80ebb3eaae43ba011c7a91b4224a9f317d8e6591af/ruff-0.14.6-py3-none-win_amd64.whl", hash = "sha256:390e6480c5e3659f8a4c8d6a0373027820419ac14fa0d2713bd8e6c3e125b8b9", size = 14432087, upload-time = "2025-11-21T14:26:10.891Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1f/93f9b0fad9470e4c829a5bb678da4012f0c710d09331b860ee555216f4ea/ruff-0.14.6-py3-none-win_arm64.whl", hash = "sha256:d43c81fbeae52cfa8728d8766bbf46ee4298c888072105815b392da70ca836b2", size = 13520930, upload-time = "2025-11-21T14:26:13.951Z" }, +version = "0.14.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/5b/dd7406afa6c95e3d8fa9d652b6d6dd17dd4a6bf63cb477014e8ccd3dcd46/ruff-0.14.7.tar.gz", hash = "sha256:3417deb75d23bd14a722b57b0a1435561db65f0ad97435b4cf9f85ffcef34ae5", size = 5727324, upload-time = "2025-11-28T20:55:10.525Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/b1/7ea5647aaf90106f6d102230e5df874613da43d1089864da1553b899ba5e/ruff-0.14.7-py3-none-linux_armv6l.whl", hash = "sha256:b9d5cb5a176c7236892ad7224bc1e63902e4842c460a0b5210701b13e3de4fca", size = 13414475, upload-time = "2025-11-28T20:54:54.569Z" }, + { url = "https://files.pythonhosted.org/packages/af/19/fddb4cd532299db9cdaf0efdc20f5c573ce9952a11cb532d3b859d6d9871/ruff-0.14.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3f64fe375aefaf36ca7d7250292141e39b4cea8250427482ae779a2aa5d90015", size = 13634613, upload-time = "2025-11-28T20:55:17.54Z" }, + { url = "https://files.pythonhosted.org/packages/40/2b/469a66e821d4f3de0440676ed3e04b8e2a1dc7575cf6fa3ba6d55e3c8557/ruff-0.14.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93e83bd3a9e1a3bda64cb771c0d47cda0e0d148165013ae2d3554d718632d554", size = 12765458, upload-time = "2025-11-28T20:55:26.128Z" }, + { url = "https://files.pythonhosted.org/packages/f1/05/0b001f734fe550bcfde4ce845948ac620ff908ab7241a39a1b39bb3c5f49/ruff-0.14.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3838948e3facc59a6070795de2ae16e5786861850f78d5914a03f12659e88f94", size = 13236412, upload-time = "2025-11-28T20:55:28.602Z" }, + { url = "https://files.pythonhosted.org/packages/11/36/8ed15d243f011b4e5da75cd56d6131c6766f55334d14ba31cce5461f28aa/ruff-0.14.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24c8487194d38b6d71cd0fd17a5b6715cda29f59baca1defe1e3a03240f851d1", size = 13182949, upload-time = "2025-11-28T20:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cf/fcb0b5a195455729834f2a6eadfe2e4519d8ca08c74f6d2b564a4f18f553/ruff-0.14.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79c73db6833f058a4be8ffe4a0913b6d4ad41f6324745179bd2aa09275b01d0b", size = 13816470, upload-time = "2025-11-28T20:55:08.203Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5d/34a4748577ff7a5ed2f2471456740f02e86d1568a18c9faccfc73bd9ca3f/ruff-0.14.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:12eb7014fccff10fc62d15c79d8a6be4d0c2d60fe3f8e4d169a0d2def75f5dad", size = 15289621, upload-time = "2025-11-28T20:55:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/53/53/0a9385f047a858ba133d96f3f8e3c9c66a31cc7c4b445368ef88ebeac209/ruff-0.14.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c623bbdc902de7ff715a93fa3bb377a4e42dd696937bf95669118773dbf0c50", size = 14975817, upload-time = "2025-11-28T20:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d7/2f1c32af54c3b46e7fadbf8006d8b9bcfbea535c316b0bd8813d6fb25e5d/ruff-0.14.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f53accc02ed2d200fa621593cdb3c1ae06aa9b2c3cae70bc96f72f0000ae97a9", size = 14284549, upload-time = "2025-11-28T20:55:06.08Z" }, + { url = "https://files.pythonhosted.org/packages/92/05/434ddd86becd64629c25fb6b4ce7637dd52a45cc4a4415a3008fe61c27b9/ruff-0.14.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:281f0e61a23fcdcffca210591f0f53aafaa15f9025b5b3f9706879aaa8683bc4", size = 14071389, upload-time = "2025-11-28T20:55:35.617Z" }, + { url = "https://files.pythonhosted.org/packages/ff/50/fdf89d4d80f7f9d4f420d26089a79b3bb1538fe44586b148451bc2ba8d9c/ruff-0.14.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:dbbaa5e14148965b91cb090236931182ee522a5fac9bc5575bafc5c07b9f9682", size = 14202679, upload-time = "2025-11-28T20:55:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/77/54/87b34988984555425ce967f08a36df0ebd339bb5d9d0e92a47e41151eafc/ruff-0.14.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1464b6e54880c0fe2f2d6eaefb6db15373331414eddf89d6b903767ae2458143", size = 13147677, upload-time = "2025-11-28T20:55:19.933Z" }, + { url = "https://files.pythonhosted.org/packages/67/29/f55e4d44edfe053918a16a3299e758e1c18eef216b7a7092550d7a9ec51c/ruff-0.14.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f217ed871e4621ea6128460df57b19ce0580606c23aeab50f5de425d05226784", size = 13151392, upload-time = "2025-11-28T20:55:21.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/69/47aae6dbd4f1d9b4f7085f4d9dcc84e04561ee7ad067bf52e0f9b02e3209/ruff-0.14.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6be02e849440ed3602d2eb478ff7ff07d53e3758f7948a2a598829660988619e", size = 13412230, upload-time = "2025-11-28T20:55:12.749Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4b/6e96cb6ba297f2ba502a231cd732ed7c3de98b1a896671b932a5eefa3804/ruff-0.14.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19a0f116ee5e2b468dfe80c41c84e2bbd6b74f7b719bee86c2ecde0a34563bcc", size = 14195397, upload-time = "2025-11-28T20:54:56.896Z" }, + { url = "https://files.pythonhosted.org/packages/69/82/251d5f1aa4dcad30aed491b4657cecd9fb4274214da6960ffec144c260f7/ruff-0.14.7-py3-none-win32.whl", hash = "sha256:e33052c9199b347c8937937163b9b149ef6ab2e4bb37b042e593da2e6f6cccfa", size = 13126751, upload-time = "2025-11-28T20:55:03.47Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b5/d0b7d145963136b564806f6584647af45ab98946660d399ec4da79cae036/ruff-0.14.7-py3-none-win_amd64.whl", hash = "sha256:e17a20ad0d3fad47a326d773a042b924d3ac31c6ca6deb6c72e9e6b5f661a7c6", size = 14531726, upload-time = "2025-11-28T20:54:59.121Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d2/1637f4360ada6a368d3265bf39f2cf737a0aaab15ab520fc005903e883f8/ruff-0.14.7-py3-none-win_arm64.whl", hash = "sha256:be4d653d3bea1b19742fcc6502354e32f65cd61ff2fbdb365803ef2c2aec6228", size = 13609215, upload-time = "2025-11-28T20:55:15.375Z" }, ] [[package]] @@ -2416,24 +2499,44 @@ name = "sphinx-prompt" version = "1.9.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version >= '3.11' and python_full_version < '3.13'", "python_full_version == '3.10.*'", ] dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.10'" }, - { name = "docutils", marker = "python_full_version >= '3.10'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "certifi", marker = "python_full_version == '3.10.*'" }, + { name = "docutils", marker = "python_full_version == '3.10.*'" }, + { name = "idna", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", marker = "python_full_version == '3.10.*'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "urllib3", marker = "python_full_version >= '3.10'" }, + { name = "urllib3", marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/fe/ac4e24f35b5148b31ac717ae7dcc7a2f7ec56eb729e22c7252ed8ad2d9a5/sphinx_prompt-1.9.0.tar.gz", hash = "sha256:471b3c6d466dce780a9b167d9541865fd4e9a80ed46e31b06a52a0529ae995a1", size = 5340, upload-time = "2024-08-07T15:46:51.428Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/76/98/e90ca466e0ede452d3e5a8d92b8fb68db6de269856e019ed9cab69440522/sphinx_prompt-1.9.0-py3-none-any.whl", hash = "sha256:fd731446c03f043d1ff6df9f22414495b23067c67011cc21658ea8d36b3575fc", size = 7311, upload-time = "2024-08-07T15:46:50.329Z" }, ] +[[package]] +name = "sphinx-prompt" +version = "1.10.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.11'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "idna", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "urllib3", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/a3/91293c0e0f0b76d0697ba7a41541929ca3f5457671d008bd84a9bde17e21/sphinx_prompt-1.10.2.tar.gz", hash = "sha256:47b592ba75caebd044b0eddf7a5a1b6e0aef6df587b034377cd101a999b686ba", size = 5566, upload-time = "2025-11-28T09:23:18.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/f4/44ce4d0179fb4e9cfe181a8aa281bba23e40158a609fb3680774529acaaa/sphinx_prompt-1.10.2-py3-none-any.whl", hash = "sha256:6594337962c4b1498602e6984634bed4a0dc7955852e3cfc255eb0af766ed859", size = 7474, upload-time = "2025-11-28T09:23:17.154Z" }, +] + [[package]] name = "sphinx-tabs" version = "3.4.5" @@ -2475,7 +2578,8 @@ dependencies = [ { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-jinja2-compat" }, { name = "sphinx-prompt", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx-prompt", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx-prompt", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx-prompt", version = "1.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-tabs" }, { name = "tabulate" }, { name = "typing-extensions" }, @@ -2818,36 +2922,31 @@ wheels = [ [[package]] name = "uuid-utils" -version = "0.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/ef/b6c1fd4fee3b2854bf9d602530ab8b6624882e2691c15a9c4d22ea8c03eb/uuid_utils-0.11.1.tar.gz", hash = "sha256:7ef455547c2ccb712840b106b5ab006383a9bfe4125ba1c5ab92e47bcbf79b46", size = 19933, upload-time = "2025-10-02T13:32:09.526Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/f5/254d7ce4b3aa4a1a3a4f279e0cc74eec8b4d3a61641d8ffc6e983907f2ca/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4bc8cf73c375b9ea11baf70caacc2c4bf7ce9bfd804623aa0541e5656f3dbeaf", size = 581019, upload-time = "2025-10-02T13:31:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/e6/f7d14c4e1988d8beb3ac9bd773f370376c704925bdfb07380f5476bb2986/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0d2cb3bcc6f5862d08a0ee868b18233bc63ba9ea0e85ea9f3f8e703983558eba", size = 294377, upload-time = "2025-10-02T13:31:34.01Z" }, - { url = "https://files.pythonhosted.org/packages/8e/40/847a9a0258e7a2a14b015afdaa06ee4754a2680db7b74bac159d594eeb18/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463400604f623969f198aba9133ebfd717636f5e34257340302b1c3ff685dc0f", size = 328070, upload-time = "2025-10-02T13:31:35.619Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/c5d342d31860c9b4f481ef31a4056825961f9b462d216555e76dcee580ea/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aef66b935342b268c6ffc1796267a1d9e73135740a10fe7e4098e1891cbcc476", size = 333610, upload-time = "2025-10-02T13:31:37.058Z" }, - { url = "https://files.pythonhosted.org/packages/e1/4b/52edc023ffcb9ab9a4042a58974a79c39ba7a565e683f1fd9814b504cf13/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd65c41b81b762278997de0d027161f27f9cc4058fa57bbc0a1aaa63a63d6d1a", size = 475669, upload-time = "2025-10-02T13:31:38.38Z" }, - { url = "https://files.pythonhosted.org/packages/59/81/ee55ee63264531bb1c97b5b6033ad6ec81b5cd77f89174e9aef3af3d8889/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfac9d5d7522d61accabb8c68448ead6407933415e67e62123ed6ed11f86510", size = 331946, upload-time = "2025-10-02T13:31:39.66Z" }, - { url = "https://files.pythonhosted.org/packages/cf/07/5d4be27af0e9648afa512f0d11bb6d96cb841dd6d29b57baa3fbf55fd62e/uuid_utils-0.11.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:003f48f05c01692d0c1f7e413d194e7299a1a364e0047a4eb904d3478b84eca1", size = 352920, upload-time = "2025-10-02T13:31:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/5b/48/a69dddd9727512b0583b87bfff97d82a8813b28fb534a183c9e37033cfef/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5c936042120bdc30d62f539165beaa4a6ba7e817a89e5409a6f06dc62c677a9", size = 509413, upload-time = "2025-10-02T13:31:42.547Z" }, - { url = "https://files.pythonhosted.org/packages/66/0d/1b529a3870c2354dd838d5f133a1cba75220242b0061f04a904ca245a131/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:2e16dcdbdf4cd34ffb31ead6236960adb50e6c962c9f4554a6ecfdfa044c6259", size = 529454, upload-time = "2025-10-02T13:31:44.338Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f2/04a3f77c85585aac09d546edaf871a4012052fb8ace6dbddd153b4d50f02/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f8b21fed11b23134502153d652c77c3a37fa841a9aa15a4e6186d440a22f1a0e", size = 498084, upload-time = "2025-10-02T13:31:45.601Z" }, - { url = "https://files.pythonhosted.org/packages/89/08/538b380b4c4b220f3222c970930fe459cc37f1dfc6c8dc912568d027f17d/uuid_utils-0.11.1-cp39-abi3-win32.whl", hash = "sha256:72abab5ab27c1b914e3f3f40f910532ae242df1b5f0ae43f1df2ef2f610b2a8c", size = 174314, upload-time = "2025-10-02T13:31:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/00/66/971ec830094ac1c7d46381678f7138c1805015399805e7dd7769c893c9c8/uuid_utils-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:5ed9962f8993ef2fd418205f92830c29344102f86871d99b57cef053abf227d9", size = 179214, upload-time = "2025-10-02T13:31:48.344Z" }, - { url = "https://files.pythonhosted.org/packages/b5/34/8caf2873f968294662dc5cc5f0a52249eaada290c1b941d808578e857d44/uuid_utils-0.11.1-pp310-pypy310_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d7d9cfb7a6d2e70782fee6f34ea4aa56de16aeb8c3ab80ad62e379d06bed213c", size = 580313, upload-time = "2025-10-02T13:31:49.641Z" }, - { url = "https://files.pythonhosted.org/packages/ef/76/87612c87fcb58b3e7b94db596ec80b777594e8a57b2c6552f6274e59202a/uuid_utils-0.11.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fb4899b1598f12ddf25861e85c760e739116d9255b766346c0c564f44826da1e", size = 294291, upload-time = "2025-10-02T13:31:50.864Z" }, - { url = "https://files.pythonhosted.org/packages/b8/47/752baf97e8322580727c85fbe96f38843191c6ecd5482f307193a315ff35/uuid_utils-0.11.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83dd427e754c527d47512cc9211ca58f770fb904c964a20c94aa14b875864426", size = 326852, upload-time = "2025-10-02T13:31:52.487Z" }, - { url = "https://files.pythonhosted.org/packages/47/f2/887f499a4f555c51a0b8f7545fad35e95b208c8ca60926c61c6506a29726/uuid_utils-0.11.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f05ede1e8b86ac5fce381530ae7e6c241dca8579aae028a33b59385195ac91aa", size = 332499, upload-time = "2025-10-02T13:31:53.684Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f2/c10356f7787efa4980993d66ef796787076457df11692e101d99b72c0a77/uuid_utils-0.11.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52e041072a402f1c7bb46bda156bc308adfe63075ac69d16f17fd4e651e016e9", size = 474585, upload-time = "2025-10-02T13:31:54.899Z" }, - { url = "https://files.pythonhosted.org/packages/04/fa/2b76936be180575af4fa8aa992b2f2082d03945e3fd56c5bef7d30336da7/uuid_utils-0.11.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4631774f439926c2488cf77a3b1afaa7ee96b94aae7cff14b7e080d375f3287", size = 330729, upload-time = "2025-10-02T13:31:56.277Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/2689fd44b1ef5fa045d4fce38977d466f18072a04a63b63739677369613d/uuid_utils-0.11.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:502de88885234f59e1bb4c2756f1369aa14a45f8aca25771c977057a8bbd595a", size = 351930, upload-time = "2025-10-02T13:31:57.872Z" }, - { url = "https://files.pythonhosted.org/packages/88/53/5d02a025954cb520d46ae7fbf47096a421faa1ddfe0a9477114aeb9410cd/uuid_utils-0.11.1-pp39-pypy39_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6ce277bc8b1c6020908d2fba29c6f018b3c6132865dda8b5ab2da3496ed5270", size = 580730, upload-time = "2025-10-02T13:31:59.578Z" }, - { url = "https://files.pythonhosted.org/packages/66/8e/a161fad3242124b0565d5383ac8a51d87ee5292350fdfabd23b300a161ae/uuid_utils-0.11.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a0e04966246e9ef73229eacf3300f53b7491e2cca90bf5c2e724c7ef9ecfa57c", size = 294481, upload-time = "2025-10-02T13:32:01.33Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b5/65d498e22d62cde1214548bd9ba2f3443bafeebee916bf860b48b872cb90/uuid_utils-0.11.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8df5deed6b1c55aae0a7d8221911ea6f4ef9c03eecf40b2442f605ca1c42e4b4", size = 327635, upload-time = "2025-10-02T13:32:02.777Z" }, - { url = "https://files.pythonhosted.org/packages/36/e6/8626f55c3ea4312268fff3de459e903eb187aaf773837faca41701405892/uuid_utils-0.11.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34c864e3c098e773eb763ca66450444759ee882413ca76e5c69d57bf689f0cfe", size = 332879, upload-time = "2025-10-02T13:32:04.24Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f8/cb0763df3b4777045f67e15aa54f9a600fdfdd9a317839729da1e5f1fc97/uuid_utils-0.11.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52c8858cbb6a6d6745db75e6e205268378c122890aad5f06367a764ed48a0aa3", size = 475165, upload-time = "2025-10-02T13:32:05.49Z" }, - { url = "https://files.pythonhosted.org/packages/04/c4/f9b303d6a6be633fb46ebf4c8e825aa0928fe1e6560f36082ae0862e23a0/uuid_utils-0.11.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96e601fde7dc2efa5af6fba79be43cac451184abb7c6b578a274ddce0678b17d", size = 331187, upload-time = "2025-10-02T13:32:06.806Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b7/0e6a8c36f50925c8d786e6925392a849289c4bfaa0613a23832ad3574929/uuid_utils-0.11.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7dfab8bb7e047bac7baeb1cc06b1570cb1aa611f87962dfe16e198212535bfdc", size = 352623, upload-time = "2025-10-02T13:32:08.45Z" }, +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/0e/512fb221e4970c2f75ca9dae412d320b7d9ddc9f2b15e04ea8e44710396c/uuid_utils-0.12.0.tar.gz", hash = "sha256:252bd3d311b5d6b7f5dfce7a5857e27bb4458f222586bb439463231e5a9cbd64", size = 20889, upload-time = "2025-12-01T17:29:55.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/43/de5cd49a57b6293b911b6a9a62fc03e55db9f964da7d5882d9edbee1e9d2/uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514", size = 603197, upload-time = "2025-12-01T17:29:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/02/fa/5fd1d8c9234e44f0c223910808cde0de43bb69f7df1349e49b1afa7f2baa/uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65", size = 305168, upload-time = "2025-12-01T17:29:31.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c6/8633ac9942bf9dc97a897b5154e5dcffa58816ec4dd780b3b12b559ff05c/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8fce83ecb3b16af29c7809669056c4b6e7cc912cab8c6d07361645de12dd79", size = 340580, upload-time = "2025-12-01T17:29:32.362Z" }, + { url = "https://files.pythonhosted.org/packages/f3/88/8a61307b04b4da1c576373003e6d857a04dade52ab035151d62cb84d5cb5/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec921769afcb905035d785582b0791d02304a7850fbd6ce924c1a8976380dfc6", size = 346771, upload-time = "2025-12-01T17:29:33.708Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fb/aab2dcf94b991e62aa167457c7825b9b01055b884b888af926562864398c/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f3b060330f5899a92d5c723547dc6a95adef42433e9748f14c66859a7396664", size = 474781, upload-time = "2025-12-01T17:29:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7a/dbd5e49c91d6c86dba57158bbfa0e559e1ddf377bb46dcfd58aea4f0d567/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:908dfef7f0bfcf98d406e5dc570c25d2f2473e49b376de41792b6e96c1d5d291", size = 343685, upload-time = "2025-12-01T17:29:36.677Z" }, + { url = "https://files.pythonhosted.org/packages/1a/19/8c4b1d9f450159733b8be421a4e1fb03533709b80ed3546800102d085572/uuid_utils-0.12.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c6a24148926bd0ca63e8a2dabf4cc9dc329a62325b3ad6578ecd60fbf926506", size = 366482, upload-time = "2025-12-01T17:29:37.979Z" }, + { url = "https://files.pythonhosted.org/packages/82/43/c79a6e45687647f80a159c8ba34346f287b065452cc419d07d2212d38420/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:64a91e632669f059ef605f1771d28490b1d310c26198e46f754e8846dddf12f4", size = 523132, upload-time = "2025-12-01T17:29:39.293Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a2/b2d75a621260a40c438aa88593827dfea596d18316520a99e839f7a5fb9d/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:93c082212470bb4603ca3975916c205a9d7ef1443c0acde8fbd1e0f5b36673c7", size = 614218, upload-time = "2025-12-01T17:29:40.315Z" }, + { url = "https://files.pythonhosted.org/packages/13/6b/ba071101626edd5a6dabf8525c9a1537ff3d885dbc210540574a03901fef/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:431b1fb7283ba974811b22abd365f2726f8f821ab33f0f715be389640e18d039", size = 546241, upload-time = "2025-12-01T17:29:41.656Z" }, + { url = "https://files.pythonhosted.org/packages/01/12/9a942b81c0923268e6d85bf98d8f0a61fcbcd5e432fef94fdf4ce2ef8748/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd7838c40149100299fa37cbd8bab5ee382372e8e65a148002a37d380df7c8", size = 511842, upload-time = "2025-12-01T17:29:43.107Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a7/c326f5163dd48b79368b87d8a05f5da4668dd228a3f5ca9d79d5fee2fc40/uuid_utils-0.12.0-cp39-abi3-win32.whl", hash = "sha256:487f17c0fee6cbc1d8b90fe811874174a9b1b5683bf2251549e302906a50fed3", size = 179088, upload-time = "2025-12-01T17:29:44.492Z" }, + { url = "https://files.pythonhosted.org/packages/38/92/41c8734dd97213ee1d5ae435cf4499705dc4f2751e3b957fd12376f61784/uuid_utils-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:9598e7c9da40357ae8fffc5d6938b1a7017f09a1acbcc95e14af8c65d48c655a", size = 183003, upload-time = "2025-12-01T17:29:45.47Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/52ab0359618987331a1f739af837d26168a4b16281c9c3ab46519940c628/uuid_utils-0.12.0-cp39-abi3-win_arm64.whl", hash = "sha256:c9bea7c5b2aa6f57937ebebeee4d4ef2baad10f86f1b97b58a3f6f34c14b4e84", size = 182975, upload-time = "2025-12-01T17:29:46.444Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f7/6c55b7722cede3b424df02ed5cddb25c19543abda2f95fa4cfc34a892ae5/uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e2209d361f2996966ab7114f49919eb6aaeabc6041672abbbbf4fdbb8ec1acc0", size = 593065, upload-time = "2025-12-01T17:29:47.507Z" }, + { url = "https://files.pythonhosted.org/packages/b8/40/ce5fe8e9137dbd5570e0016c2584fca43ad81b11a1cef809a1a1b4952ab7/uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d9636bcdbd6cfcad2b549c352b669412d0d1eb09be72044a2f13e498974863cd", size = 300047, upload-time = "2025-12-01T17:29:48.596Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9b/31c5d0736d7b118f302c50214e581f40e904305d8872eb0f0c921d50e138/uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cd8543a3419251fb78e703ce3b15fdfafe1b7c542cf40caf0775e01db7e7674", size = 335165, upload-time = "2025-12-01T17:29:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/f6/5c/d80b4d08691c9d7446d0ad58fd41503081a662cfd2c7640faf68c64d8098/uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e98db2d8977c052cb307ae1cb5cc37a21715e8d415dbc65863b039397495a013", size = 341437, upload-time = "2025-12-01T17:29:51.112Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b3/9dccdc6f3c22f6ef5bd381ae559173f8a1ae185ae89ed1f39f499d9d8b02/uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8f2bdf5e4ffeb259ef6d15edae92aed60a1d6f07cbfab465d836f6b12b48da8", size = 469123, upload-time = "2025-12-01T17:29:52.389Z" }, + { url = "https://files.pythonhosted.org/packages/fd/90/6c35ef65fbc49f8189729839b793a4a74a7dd8c5aa5eb56caa93f8c97732/uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c3ec53c0cb15e1835870c139317cc5ec06e35aa22843e3ed7d9c74f23f23898", size = 335892, upload-time = "2025-12-01T17:29:53.44Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c7/e3f3ce05c5af2bf86a0938d22165affe635f4dcbfd5687b1dacc042d3e0e/uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84e5c0eba209356f7f389946a3a47b2cc2effd711b3fc7c7f155ad9f7d45e8a3", size = 360693, upload-time = "2025-12-01T17:29:54.558Z" }, ] [[package]]