diff --git a/optuna_mcp/server.py b/optuna_mcp/server.py index 20c5261..2c1c3e9 100644 --- a/optuna_mcp/server.py +++ b/optuna_mcp/server.py @@ -7,9 +7,18 @@ from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import Image +from mcp.shared.exceptions import McpError +from mcp.types import ErrorData +from mcp.types import INTERNAL_ERROR import optuna import optuna_dashboard import plotly +from pydantic import BaseModel +from pydantic import Field + + +SamplerName = typing.Literal["TPESampler", "NSGAIISampler", "RandomSampler", "GPSampler"] +DirectionName = typing.Literal["minimize", "maximize"] class OptunaMCP(FastMCP): @@ -59,12 +68,41 @@ class TrialToAdd: system_attrs: dict[str, typing.Any] | None +class StudyResponse(BaseModel): + study_name: str + sampler_name: SamplerName | None = Field( + default=None, description="The name of the sampler used in the study." + ) + directions: list[DirectionName] | None = Field( + default=None, description="The optimization directions for each objective." + ) + metric_names: list[str] | None = Field( + default=None, description="The metric names for each objective." + ) + + +class TrialResponse(BaseModel): + trial_number: int + params: dict[str, typing.Any] | None = Field( + default=None, description="The parameter values suggested by the trial." + ) + values: list[float] | None = Field( + default=None, description="The objective values of the trial." + ) + user_attrs: dict[str, typing.Any] | None = Field( + default=None, description="User-defined attributes for the trial." + ) + system_attrs: dict[str, typing.Any] | None = Field( + default=None, description="System-defined attributes for the trial." + ) + + def register_tools(mcp: OptunaMCP) -> OptunaMCP: - @mcp.tool() + @mcp.tool(structured_output=True) def create_study( study_name: str, - directions: list[typing.Literal["minimize", "maximize"]] | None = None, - ) -> str: + directions: list[DirectionName] | None = None, + ) -> StudyResponse: """Create a new Optuna study with the given study_name and directions. If the study already exists, it will be simply loaded. @@ -79,10 +117,10 @@ def create_study( if mcp.storage is None: mcp.storage = mcp.study._storage - return f"Optuna study {study_name} has been prepared" + return StudyResponse(study_name=study_name) - @mcp.tool() - def get_all_study_names() -> str: + @mcp.tool(structured_output=True) + def get_all_study_names() -> list[StudyResponse]: """Get all study names from the storage.""" storage: str | optuna.storages.BaseStorage | None = None if mcp.study is not None: @@ -90,13 +128,13 @@ def get_all_study_names() -> str: elif mcp.storage is not None: storage = mcp.storage else: - return "No storage specified." + raise McpError(ErrorData(code=INTERNAL_ERROR, message="No storage specified.")) study_names = optuna.get_all_study_names(storage) - return f"All study names: {study_names}" + return [StudyResponse(study_name=name) for name in study_names] - @mcp.tool() - def ask(search_space: dict) -> str: + @mcp.tool(structured_output=True) + def ask(search_space: dict) -> TrialResponse: """Suggest new parameters using Optuna search_space must be a string that can be evaluated to a dictionary to specify Optuna's distributions. @@ -110,20 +148,33 @@ def ask(search_space: dict) -> str: for name, dist in search_space.items() } except Exception as e: - return f"Error: {e}" + raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Error: {e}")) from e if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) trial = mcp.study.ask(fixed_distributions=distributions) - return f"Trial {trial.number} suggested: {json.dumps(trial.params)}" + return TrialResponse( + trial_number=trial.number, + params=trial.params, + ) - @mcp.tool() - def tell(trial_number: int, values: float | list[float]) -> str: + @mcp.tool(structured_output=True) + def tell(trial_number: int, values: float | list[float]) -> TrialResponse: """Report the result of a trial""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) mcp.study.tell( trial=trial_number, @@ -131,12 +182,15 @@ def tell(trial_number: int, values: float | list[float]) -> str: state=optuna.trial.TrialState.COMPLETE, skip_if_finished=True, ) - return f"Trial {trial_number} reported with values {json.dumps(values)}" + return TrialResponse( + trial_number=trial_number, + values=[values] if isinstance(values, float) else values, + ) - @mcp.tool() + @mcp.tool(structured_output=True) def set_sampler( - name: typing.Literal["TPESampler", "NSGAIISampler", "RandomSampler", "GPSampler"], - ) -> str: + name: SamplerName, + ) -> StudyResponse: """Set the sampler for the study. The sampler must be one of the following: - TPESampler @@ -150,15 +204,29 @@ def set_sampler( """ sampler = getattr(optuna.samplers, name)() if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + mcp.study.sampler = sampler - return f"Sampler set to {name}" + return StudyResponse( + study_name=mcp.study.study_name, + sampler_name=name, + ) - @mcp.tool() + @mcp.tool(structured_output=True) def set_trial_user_attr(trial_number: int, key: str, value: typing.Any) -> str: """Set user attributes for a trial""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) storage = mcp.study._storage trial_id = storage.get_trial_id_from_study_id_trial_number( @@ -167,20 +235,29 @@ def set_trial_user_attr(trial_number: int, key: str, value: typing.Any) -> str: storage.set_trial_user_attr(trial_id, key, value) return f"User attribute {key} set to {json.dumps(value)} for trial {trial_number}" - @mcp.tool() - def get_trial_user_attrs(trial_number: int) -> str: + @mcp.tool(structured_output=True) + def get_trial_user_attrs(trial_number: int) -> TrialResponse: """Get user attributes in a trial""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + storage = mcp.study._storage trial_id = storage.get_trial_id_from_study_id_trial_number( mcp.study._study_id, trial_number ) trial = storage.get_trial(trial_id) - return f"User attributes in trial {trial_number}: {json.dumps(trial.user_attrs)}" + return TrialResponse( + trial_number=trial_number, + user_attrs=trial.user_attrs, + ) - @mcp.tool() - def set_metric_names(metric_names: list[str]) -> str: + @mcp.tool(structured_output=True) + def set_metric_names(metric_names: list[str]) -> StudyResponse: """Set metric_names. metric_names are labels used to distinguish what each objective value is. Args: @@ -189,51 +266,109 @@ def set_metric_names(metric_names: list[str]) -> str: The length of metric_names list must be the same with the number of objectives. """ if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) mcp.study.set_metric_names(metric_names) - return f"metric_names set to {json.dumps(metric_names)}" + return StudyResponse( + study_name=mcp.study.study_name, + metric_names=metric_names, + ) - @mcp.tool() - def get_metric_names() -> str: + @mcp.tool(structured_output=True) + def get_metric_names() -> StudyResponse: """Get metric_names""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") - return f"Metric names: {json.dumps(mcp.study.metric_names)}" + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) - @mcp.tool() - def get_directions() -> str: + return StudyResponse( + study_name=mcp.study.study_name, + metric_names=mcp.study.metric_names, + ) + + @mcp.tool(structured_output=True) + def get_directions() -> StudyResponse: """Get the directions of the study.""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + directions = [d.name.lower() for d in mcp.study.directions] - return f"Directions: {json.dumps(directions)}" + return StudyResponse( + study_name=mcp.study.study_name, + directions=typing.cast(list[DirectionName], directions), + ) - @mcp.tool() + @mcp.tool(structured_output=False) def get_trials() -> str: """Get all trials in a CSV format""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + csv_string = mcp.study.trials_dataframe().to_csv() return f"Trials: \n{csv_string}" - @mcp.tool() - def best_trial() -> str: + @mcp.tool(structured_output=True) + def best_trial() -> TrialResponse: """Get the best trial This feature can only be used for single-objective optimization. If your study is multi-objective, use best_trials instead. """ if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) trial = mcp.study.best_trial - return f"Best trial: {trial.number} with params {json.dumps(trial.params)} and value {json.dumps(trial.value)}" + return TrialResponse( + trial_number=trial.number, + params=trial.params, + values=trial.values, + user_attrs=trial.user_attrs, + system_attrs=trial.system_attrs, + ) - @mcp.tool() - def best_trials() -> str: + @mcp.tool(structured_output=True) + def best_trials() -> list[TrialResponse]: """Return trials located at the Pareto front in the study.""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") - return f"Best trials: {mcp.study.best_trials}" + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + + return [ + TrialResponse( + trial_number=trial.number, + params=trial.params, + values=trial.values, + user_attrs=trial.user_attrs, + system_attrs=trial.system_attrs, + ) + for trial in mcp.study.best_trials + ] def _create_trial(trial: TrialToAdd) -> optuna.trial.FrozenTrial: """Create a trial from the given parameters.""" @@ -249,20 +384,32 @@ def _create_trial(trial: TrialToAdd) -> optuna.trial.FrozenTrial: system_attrs=trial.system_attrs, ) - @mcp.tool() + @mcp.tool(structured_output=True) def add_trial(trial: TrialToAdd) -> str: """Add a trial to the study.""" if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + mcp.study.add_trial(_create_trial(trial)) return "Trial was added." - @mcp.tool() + @mcp.tool(structured_output=True) def add_trials(trials: list[TrialToAdd]) -> str: """Add multiple trials to the study.""" frozen_trials = [_create_trial(trial) for trial in trials] if mcp.study is None: - raise ValueError("No study has been created. Please create a study first.") + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) + ) + mcp.study.add_trials(frozen_trials) return f"{len(trials)} trials were added." @@ -482,7 +629,7 @@ def plot_rank( fig = optuna.visualization.plot_rank(mcp.study) return Image(data=plotly.io.to_image(fig), format="png") - @mcp.tool() + @mcp.tool(structured_output=True) def launch_optuna_dashboard(port: int = 58080) -> str: """Launch the Optuna dashboard""" storage: str | optuna.storages.BaseStorage | None = None @@ -494,9 +641,11 @@ def launch_optuna_dashboard(port: int = 58080) -> str: elif mcp.storage is not None: storage = mcp.storage else: - raise ValueError( - "No study has been created or no storage URL has been provided." - "Please create a study or provide a storage URL directly." + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message="No study has been created. Please create a study first.", + ) ) def runner(storage: optuna.storages.BaseStorage | str, port: int) -> None: diff --git a/pyproject.toml b/pyproject.toml index e2ffe18..b0138b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ requires-python = ">=3.12" dependencies = [ "kaleido==0.2.1", - "mcp[cli]>=1.5.0", + "mcp[cli]>=1.10.0", "optuna>=4.2.1", "optuna-dashboard>=0.18.0", "pandas>=2.2.3", diff --git a/tests/test_server.py b/tests/test_server.py index 3e283e5..c64d75d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -2,10 +2,11 @@ from collections.abc import Sequence import json from pathlib import Path +import typing from typing import Any from unittest.mock import patch -from mcp.types import EmbeddedResource +from mcp.server.fastmcp.exceptions import ToolError from mcp.types import ImageContent from mcp.types import TextContent import optuna @@ -16,10 +17,14 @@ from optuna_mcp.server import OptunaMCP from optuna_mcp.server import register_tools +from optuna_mcp.server import SamplerName +from optuna_mcp.server import StudyResponse +from optuna_mcp.server import TrialResponse from optuna_mcp.server import TrialToAdd STORAGE_MODES: list[str] = ["inmemory", "sqlite"] +SAMPLER_NAME = typing.get_args(SamplerName) @pytest.fixture(params=STORAGE_MODES) @@ -86,15 +91,18 @@ async def test_create_study(mcp: OptunaMCP) -> None: @pytest.mark.anyio async def test_get_all_study_names(mcp: OptunaMCP) -> None: - result: Sequence[TextContent | ImageContent | EmbeddedResource] = await mcp.call_tool( - "get_all_study_names", arguments={} - ) - assert len(result) == 1 - assert isinstance(result[0], TextContent) if mcp.storage is None: - assert result[0].text == "No storage specified." + with pytest.raises(ToolError) as e: + await mcp.call_tool("get_all_study_names", arguments={}) + assert "No storage specified" in str(e.value) else: - assert "[]" in result[0].text + result = await mcp.call_tool("get_all_study_names", arguments={}) + assert len(result) == 2 + assert isinstance(result, Sequence) + assert isinstance(result[0], list) + assert result[0] == [] + assert isinstance(result[1], dict) + assert result[1]["result"] == [] @pytest.mark.parametrize( @@ -110,8 +118,10 @@ async def test_ask(mcp: OptunaMCP, search_space: dict) -> None: assert mcp.study is not None result = await mcp.call_tool("ask", arguments={"search_space": search_space}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(TrialResponse(**result[1]), TrialResponse) assert len(mcp.study.trials) == 1 assert len(mcp.study.trials[0].params) == len(search_space) for key in search_space: @@ -134,24 +144,27 @@ async def test_tell(mcp: OptunaMCP, directions: list[str], values: list[float]) t = mcp.study.ask() result = await mcp.call_tool("tell", arguments={"trial_number": t.number, "values": values}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(TrialResponse(**result[1]), TrialResponse) assert len(mcp.study.trials) == 1 assert mcp.study.trials[0].values == values -@pytest.mark.parametrize( - "sampler_name", ["TPESampler", "NSGAIISampler", "RandomSampler", "GPSampler"] -) +@pytest.mark.parametrize("sampler_name", SAMPLER_NAME) @pytest.mark.anyio async def test_set_sampler(mcp: OptunaMCP, sampler_name: str) -> None: await mcp.call_tool("create_study", arguments={"study_name": "test_study"}) assert mcp.study is not None result = await mcp.call_tool("set_sampler", arguments={"name": sampler_name}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - assert sampler_name in result[0].text + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(StudyResponse(**result[1]), StudyResponse) + assert sampler_name == json.loads(result[0][0].text)["sampler_name"] + assert sampler_name == result[1]["sampler_name"] assert isinstance(mcp.study.sampler, getattr(optuna.samplers, sampler_name)) @@ -165,11 +178,16 @@ async def test_trial_user_attrs(mcp: OptunaMCP) -> None: ) result = await mcp.call_tool("get_trial_user_attrs", arguments={"trial_number": t.number}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - r = json.loads(":".join(result[0].text.split(":")[1:]).strip()) - assert isinstance(r, dict) - assert r == {"abc": "def"} + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(TrialResponse(**result[1]), TrialResponse) + user_attrs_from_text = json.loads(result[0][0].text)["user_attrs"] + user_attrs_from_dict = result[1]["user_attrs"] + + for r in (user_attrs_from_text, user_attrs_from_dict): + assert isinstance(r, dict) + assert r == {"abc": "def"} @pytest.mark.parametrize( @@ -189,11 +207,12 @@ async def test_metric_names(mcp: OptunaMCP, metric_names: list[str]) -> None: await mcp.call_tool("set_metric_names", arguments={"metric_names": metric_names}) result = await mcp.call_tool("get_metric_names", arguments={}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - r = json.loads(":".join(result[0].text.split(":")[1:]).strip()) - assert isinstance(r, list) - assert r == metric_names + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(StudyResponse(**result[1]), StudyResponse) + assert metric_names == json.loads(result[0][0].text)["metric_names"] + assert metric_names == result[1]["metric_names"] @pytest.mark.parametrize( @@ -211,11 +230,17 @@ async def test_get_directions(mcp: OptunaMCP, directions: list[str]) -> None: ) result = await mcp.call_tool("get_directions", arguments={}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - r = json.loads(":".join(result[0].text.split(":")[1:]).strip()) - assert isinstance(r, list) - assert r == directions + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(StudyResponse(**result[1]), StudyResponse) + + directions_from_text = json.loads(result[0][0].text)["directions"] + directions_from_dict = result[1]["directions"] + + for r in (directions_from_text, directions_from_dict): + assert isinstance(r, list) + assert r == directions @pytest.mark.anyio @@ -242,9 +267,12 @@ async def test_best_trial(mcp: OptunaMCP) -> None: mcp.study.tell(t, [0.0]) result = await mcp.call_tool("best_trial", arguments={}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - assert result[0].text.strip().split(" ")[-1] == "0.0" + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(TrialResponse(**result[1]), TrialResponse) + assert json.loads(result[0][0].text)["values"] == [0.0] + assert result[1]["values"] == [0.0] @pytest.mark.anyio @@ -258,9 +286,10 @@ async def test_best_trials(mcp: OptunaMCP) -> None: mcp.study.tell(t, [0.0, 1.1]) result = await mcp.call_tool("best_trials", arguments={}) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - assert result[0].text.strip().endswith("]") + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert isinstance(TrialResponse(**result[1]["result"][0]), TrialResponse) @pytest.mark.anyio @@ -492,9 +521,12 @@ async def test_launch_optuna_dashboard( arguments = {} if port is None else {"port": port} result = await mcp.call_tool("launch_optuna_dashboard", arguments=arguments) assert isinstance(result, Sequence) - assert len(result) == 1 - assert isinstance(result[0], TextContent) - assert result[0].text.endswith(f":{expected_port}") + assert len(result) == 2 + assert isinstance(result[0], list) + assert isinstance(result[0][0], TextContent) + assert result[0][0].text.endswith(f":{expected_port}") + assert isinstance(result[1], dict) + assert result[1]["result"].endswith(f":{expected_port}") assert mcp.dashboard_thread_port is not None assert mcp.dashboard_thread_port[0] is not None diff --git a/uv.lock b/uv.lock index b250a97..6956179 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "sys_platform != 'darwin'", @@ -281,6 +281,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, ] +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "kaleido" version = "0.2.1" @@ -358,22 +385,24 @@ wheels = [ [[package]] name = "mcp" -version = "1.9.3" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "httpx-sse" }, + { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/df/8fefc0c6c7a5c66914763e3ff3893f9a03435628f6625d5e3b0dc45d73db/mcp-1.9.3.tar.gz", hash = "sha256:587ba38448e81885e5d1b84055cfcc0ca56d35cd0c58f50941cab01109405388", size = 333045, upload-time = "2025-06-05T15:48:25.681Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/fd/d6e941a52446198b73e5e4a953441f667f1469aeb06fb382d9f6729d6168/mcp-1.14.0.tar.gz", hash = "sha256:2e7d98b195e08b2abc1dc6191f6f3dc0059604ac13ee6a40f88676274787fac4", size = 454855, upload-time = "2025-09-11T17:40:48.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/45/823ad05504bea55cb0feb7470387f151252127ad5c72f8882e8fe6cf5c0e/mcp-1.9.3-py3-none-any.whl", hash = "sha256:69b0136d1ac9927402ed4cf221d4b8ff875e7132b0b06edd446448766f34f9b9", size = 131063, upload-time = "2025-06-05T15:48:24.171Z" }, + { url = "https://files.pythonhosted.org/packages/04/7b/84b0dd4c2c5a499d2c5d63fb7a1224c25fc4c8b6c24623fa7a566471480d/mcp-1.14.0-py3-none-any.whl", hash = "sha256:b2d27feba27b4c53d41b58aa7f4d090ae0cb740cbc4e339af10f8cbe54c4e19d", size = 163805, upload-time = "2025-09-11T17:40:46.891Z" }, ] [package.optional-dependencies] @@ -564,7 +593,7 @@ dev = [ requires-dist = [ { name = "bottle", specifier = ">=0.13.4" }, { name = "kaleido", specifier = "==0.2.1" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.5.0" }, + { name = "mcp", extras = ["cli"], specifier = ">=1.10.0" }, { name = "optuna", specifier = ">=4.2.1" }, { name = "optuna-dashboard", specifier = ">=0.18.0" }, { name = "pandas", specifier = ">=2.2.3" }, @@ -836,6 +865,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -862,6 +907,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + [[package]] name = "rich" version = "14.0.0" @@ -875,6 +934,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, ] +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, + { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, + { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, +] + [[package]] name = "ruff" version = "0.11.13" @@ -1127,6 +1267,7 @@ wheels = [ { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3bf2db5adf77b433844f080887ade049c4705ddf9fe1a32023ff84ff735aa5ad" }, { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8f8b3cfc53010a4b4a3c7ecb88c212e9decc4f5eeb6af75c3c803937d2d60947" }, { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:0bc887068772233f532b51a3e8c8cfc682ae62bef74bf4e0c53526c8b9e4138f" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:a2618775f32eb4126c5b2050686da52001a08cffa331637d9cf51c8250931e00" }, { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:eb17646792ac4374ffc87e42369f45d21eff17c790868963b90483ef0b6db4ef" }, { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:84ea1f6a1d15663037d01b121d6e33bb9da3c90af8e069e5072c30f413455a57" }, { url = "https://download.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:b66f77f6f67317344ee083aa7ac4751a14395fcb38060d564bf513978d267153" },