From 3f21bd991228e1d742b571447b403906fb3b2553 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 19 Feb 2026 15:47:21 +0530 Subject: [PATCH 01/22] Add stdio mcp support to afm-core --- .../packages/afm-core/src/afm/cli.py | 8 +- .../packages/afm-core/src/afm/models.py | 21 ++- .../packages/afm-core/src/afm/variables.py | 13 +- .../packages/afm-core/tests/conftest.py | 5 + .../fixtures/sample_stdio_mcp_agent.afm.md | 36 +++++ .../packages/afm-core/tests/test_parser.py | 152 +++++++++++++++++- 6 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 python-interpreter/packages/afm-core/tests/fixtures/sample_stdio_mcp_agent.afm.md diff --git a/python-interpreter/packages/afm-core/src/afm/cli.py b/python-interpreter/packages/afm-core/src/afm/cli.py index afd1da9..34be055 100644 --- a/python-interpreter/packages/afm-core/src/afm/cli.py +++ b/python-interpreter/packages/afm-core/src/afm/cli.py @@ -39,6 +39,7 @@ ) from .models import ( ConsoleChatInterface, + HttpTransport, WebChatInterface, WebhookInterface, ) @@ -232,7 +233,12 @@ def format_validation_output(afm: AFMRecord) -> str: lines.append("") lines.append(" MCP Servers:") for server in afm.metadata.tools.mcp: - lines.append(f" - {server.name}: {server.transport.url}") + transport = server.transport + if isinstance(transport, HttpTransport): + transport_info = transport.url + else: + transport_info = transport.command + lines.append(f" - {server.name}: {transport_info}") if server.tool_filter: if server.tool_filter.allow: lines.append(f" Allow: {', '.join(server.tool_filter.allow)}") diff --git a/python-interpreter/packages/afm-core/src/afm/models.py b/python-interpreter/packages/afm-core/src/afm/models.py index daaa777..e9b4f03 100644 --- a/python-interpreter/packages/afm-core/src/afm/models.py +++ b/python-interpreter/packages/afm-core/src/afm/models.py @@ -64,11 +64,7 @@ class Model(BaseModel): authentication: ClientAuthentication | None = None -class TransportType(str, Enum): - HTTP = "http" - - -class Transport(BaseModel): +class HttpTransport(BaseModel): model_config = ConfigDict(extra="forbid") type: Literal["http"] = "http" @@ -76,6 +72,21 @@ class Transport(BaseModel): authentication: ClientAuthentication | None = None +class StdioTransport(BaseModel): + model_config = ConfigDict(extra="forbid") + + type: Literal["stdio"] = "stdio" + command: str + args: list[str] | None = None + env: dict[str, str] | None = None + + +Transport = Annotated[ + HttpTransport | StdioTransport, + Field(discriminator="type"), +] + + class ToolFilter(BaseModel): model_config = ConfigDict(extra="forbid") diff --git a/python-interpreter/packages/afm-core/src/afm/variables.py b/python-interpreter/packages/afm-core/src/afm/variables.py index 2d6e512..4edc4fa 100644 --- a/python-interpreter/packages/afm-core/src/afm/variables.py +++ b/python-interpreter/packages/afm-core/src/afm/variables.py @@ -23,6 +23,7 @@ from .exceptions import AFMValidationError, VariableResolutionError from .models import ( ConsoleChatInterface, + HttpTransport, WebChatInterface, WebhookInterface, ) @@ -178,10 +179,14 @@ def validate_http_variables(afm_record: AFMRecord) -> None: for server in metadata.tools.mcp: if contains_http_variable(server.name): errored_fields.append("tools.mcp.name") - if contains_http_variable(server.transport.url): - errored_fields.append("tools.mcp.transport.url") - if _authentication_contains_http_variable(server.transport.authentication): - errored_fields.append("tools.mcp.transport.authentication") + # HTTP transport fields: url and authentication + if isinstance(server.transport, HttpTransport): + if contains_http_variable(server.transport.url): + errored_fields.append("tools.mcp.transport.url") + if _authentication_contains_http_variable( + server.transport.authentication + ): + errored_fields.append("tools.mcp.transport.authentication") if _tool_filter_contains_http_variable(server.tool_filter): errored_fields.append("tools.mcp.tool_filter") diff --git a/python-interpreter/packages/afm-core/tests/conftest.py b/python-interpreter/packages/afm-core/tests/conftest.py index 947d26b..e3c7939 100644 --- a/python-interpreter/packages/afm-core/tests/conftest.py +++ b/python-interpreter/packages/afm-core/tests/conftest.py @@ -47,3 +47,8 @@ def sample_minimal_path(fixtures_dir: Path) -> Path: @pytest.fixture def sample_no_frontmatter_path(fixtures_dir: Path) -> Path: return fixtures_dir / "sample_no_frontmatter.afm.md" + + +@pytest.fixture +def sample_stdio_mcp_path(fixtures_dir: Path) -> Path: + return fixtures_dir / "sample_stdio_mcp_agent.afm.md" diff --git a/python-interpreter/packages/afm-core/tests/fixtures/sample_stdio_mcp_agent.afm.md b/python-interpreter/packages/afm-core/tests/fixtures/sample_stdio_mcp_agent.afm.md new file mode 100644 index 0000000..75515f5 --- /dev/null +++ b/python-interpreter/packages/afm-core/tests/fixtures/sample_stdio_mcp_agent.afm.md @@ -0,0 +1,36 @@ +--- +spec_version: '0.3.0' +name: "StdioMcpAgent" +description: "A test agent with stdio MCP tools." +version: "1.0.0" +tools: + mcp: + - name: "filesystem_server" + transport: + type: stdio + command: "npx" + args: + - "-y" + - "@modelcontextprotocol/server-filesystem" + - "/tmp" + - name: "local_db_tool" + transport: + type: stdio + command: "python" + args: + - "server.py" + env: + DB_PATH: "./data.db" + API_KEY: "dummy-key" + tool_filter: + allow: + - "query" + - "search" + deny: + - "delete" +--- +# Role +You are a file system and database assistant. + +# Instructions +Use the available stdio tools to interact with the file system and database. diff --git a/python-interpreter/packages/afm-core/tests/test_parser.py b/python-interpreter/packages/afm-core/tests/test_parser.py index 0a12cbf..0b517ff 100644 --- a/python-interpreter/packages/afm-core/tests/test_parser.py +++ b/python-interpreter/packages/afm-core/tests/test_parser.py @@ -19,7 +19,13 @@ import pytest from afm.exceptions import AFMParseError, AFMValidationError, VariableResolutionError -from afm.models import ConsoleChatInterface, WebChatInterface, WebhookInterface +from afm.models import ( + ConsoleChatInterface, + HttpTransport, + StdioTransport, + WebChatInterface, + WebhookInterface, +) from afm.parser import parse_afm, parse_afm_file @@ -56,6 +62,7 @@ def test_parse_full_agent(self, sample_agent_path: Path) -> None: assert len(result.metadata.tools.mcp) == 1 mcp_server = result.metadata.tools.mcp[0] assert mcp_server.name == "TestServer" + assert isinstance(mcp_server.transport, HttpTransport) assert mcp_server.transport.url == "https://test-server.com/api" assert mcp_server.transport.authentication is not None assert mcp_server.transport.authentication.type == "bearer" @@ -261,6 +268,149 @@ def test_parse_nonexistent_file(self) -> None: parse_afm_file("/nonexistent/path/agent.afm.md") +class TestParseStdioMcpTransport: + def test_parse_stdio_mcp_agent(self, sample_stdio_mcp_path: Path) -> None: + content = sample_stdio_mcp_path.read_text() + result = parse_afm(content) + + assert result.metadata.name == "StdioMcpAgent" + assert result.metadata.tools is not None + assert result.metadata.tools.mcp is not None + assert len(result.metadata.tools.mcp) == 2 + + # First server: no args env, no tool_filter + server1 = result.metadata.tools.mcp[0] + assert server1.name == "filesystem_server" + assert isinstance(server1.transport, StdioTransport) + assert server1.transport.type == "stdio" + assert server1.transport.command == "npx" + assert server1.transport.args == [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/tmp", + ] + assert server1.transport.env is None + assert server1.tool_filter is None + + # Second server: has env and tool_filter + server2 = result.metadata.tools.mcp[1] + assert server2.name == "local_db_tool" + assert isinstance(server2.transport, StdioTransport) + assert server2.transport.type == "stdio" + assert server2.transport.command == "python" + assert server2.transport.args == ["server.py"] + assert server2.transport.env == {"DB_PATH": "./data.db", "API_KEY": "dummy-key"} + assert server2.tool_filter is not None + assert server2.tool_filter.allow == ["query", "search"] + assert server2.tool_filter.deny == ["delete"] + + def test_parse_stdio_transport_inline(self) -> None: + content = """--- +spec_version: "0.3.0" +tools: + mcp: + - name: "local_tool" + transport: + type: stdio + command: "python" + args: + - "server.py" +--- + +# Role +Test role. + +# Instructions +Test instructions. +""" + result = parse_afm(content) + + assert result.metadata.tools is not None + assert result.metadata.tools.mcp is not None + mcp_server = result.metadata.tools.mcp[0] + assert isinstance(mcp_server.transport, StdioTransport) + assert mcp_server.transport.command == "python" + assert mcp_server.transport.args == ["server.py"] + assert mcp_server.transport.env is None + + def test_parse_http_transport_produces_http_transport_instance(self) -> None: + content = """--- +spec_version: "0.3.0" +tools: + mcp: + - name: "remote_tool" + transport: + type: http + url: "https://example.com/mcp" +--- + +# Role +Test role. + +# Instructions +Test instructions. +""" + result = parse_afm(content) + + assert result.metadata.tools is not None + assert result.metadata.tools.mcp is not None + mcp_server = result.metadata.tools.mcp[0] + assert isinstance(mcp_server.transport, HttpTransport) + assert mcp_server.transport.url == "https://example.com/mcp" + + def test_parse_stdio_transport_missing_command_raises_error(self) -> None: + content = """--- +spec_version: "0.3.0" +tools: + mcp: + - name: "broken_tool" + transport: + type: stdio +--- + +# Role +Test role. + +# Instructions +Test instructions. +""" + with pytest.raises(AFMValidationError): + parse_afm(content) + + def test_parse_mixed_http_and_stdio_transports(self) -> None: + content = """--- +spec_version: "0.3.0" +tools: + mcp: + - name: "remote_server" + transport: + type: http + url: "https://api.example.com/mcp" + - name: "local_server" + transport: + type: stdio + command: "npx" + args: + - "-y" + - "@modelcontextprotocol/server-filesystem" + - "/tmp" +--- + +# Role +Test role. + +# Instructions +Test instructions. +""" + result = parse_afm(content) + + assert result.metadata.tools is not None + assert result.metadata.tools.mcp is not None + assert len(result.metadata.tools.mcp) == 2 + assert isinstance(result.metadata.tools.mcp[0].transport, HttpTransport) + assert isinstance(result.metadata.tools.mcp[1].transport, StdioTransport) + + class TestResolveEnvParameter: def test_parse_afm_without_resolve_env_preserves_variables(self) -> None: """Test that resolve_env=False preserves ${env:VAR} syntax in string fields.""" From 9c91c63ce575b8a7b011d051c4b92930b529b6d9 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 19 Feb 2026 15:49:35 +0530 Subject: [PATCH 02/22] Add stdio mcp support to afm-langchain --- .../src/afm_langchain/tools/mcp.py | 50 +++-- .../packages/afm-langchain/tests/test_mcp.py | 186 +++++++++++++++--- 2 files changed, 190 insertions(+), 46 deletions(-) diff --git a/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py b/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py index f683cf8..591fd42 100644 --- a/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py +++ b/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py @@ -21,7 +21,7 @@ import httpx from langchain_core.tools import BaseTool from langchain_mcp_adapters.client import MultiServerMCPClient -from langchain_mcp_adapters.sessions import StreamableHttpConnection +from langchain_mcp_adapters.sessions import StdioConnection, StreamableHttpConnection from afm.exceptions import ( MCPAuthenticationError, @@ -31,7 +31,9 @@ from afm.models import ( AFMRecord, ClientAuthentication, + HttpTransport, MCPServer, + StdioTransport, ToolFilter, ) @@ -125,14 +127,12 @@ class MCPClient: def __init__( self, name: str, - url: str, - authentication: ClientAuthentication | None = None, + transport: HttpTransport | StdioTransport, tool_filter: ToolFilter | None = None, ) -> None: """Initialize an MCP client.""" self.name = name - self.url = url - self.authentication = authentication + self.transport = transport self.tool_filter = tool_filter self._tools: list[BaseTool] | None = None @@ -140,31 +140,39 @@ def __init__( def from_mcp_server(cls, server: MCPServer) -> "MCPClient": transport = server.transport - if transport.type != "http": + if not isinstance(transport, (HttpTransport, StdioTransport)): raise MCPError( - f"Unsupported transport type: {transport.type}. Only 'http' is supported for now.", + f"Unsupported transport type: {transport.type}", server_name=server.name, ) return cls( name=server.name, - url=transport.url, - authentication=transport.authentication, + transport=transport, tool_filter=server.tool_filter, ) - def _build_connection_config(self) -> StreamableHttpConnection: - config: StreamableHttpConnection = { - "transport": "streamable_http", - "url": self.url, - } - - # Add authentication if configured - auth = build_httpx_auth(self.authentication) - if auth is not None: - config["auth"] = auth - - return config + def _build_connection_config(self) -> StreamableHttpConnection | StdioConnection: + if isinstance(self.transport, HttpTransport): + config: StreamableHttpConnection = { + "transport": "streamable_http", + "url": self.transport.url, + } + auth = build_httpx_auth(self.transport.authentication) + if auth is not None: + config["auth"] = auth + return config + + else: + # StdioTransport + config: StdioConnection = { + "transport": "stdio", + "command": self.transport.command, + "args": self.transport.args or [], + } + if self.transport.env is not None: + config["env"] = self.transport.env + return config async def get_tools(self) -> list[BaseTool]: try: diff --git a/python-interpreter/packages/afm-langchain/tests/test_mcp.py b/python-interpreter/packages/afm-langchain/tests/test_mcp.py index 564f463..fe6000f 100644 --- a/python-interpreter/packages/afm-langchain/tests/test_mcp.py +++ b/python-interpreter/packages/afm-langchain/tests/test_mcp.py @@ -14,21 +14,24 @@ # specific language governing permissions and limitations # under the License. +from typing import cast from unittest.mock import AsyncMock, MagicMock, patch import httpx import pytest from langchain_core.tools import BaseTool +from langchain_mcp_adapters.sessions import StdioConnection -from afm.exceptions import MCPConnectionError, MCPError +from afm.exceptions import MCPConnectionError from afm.models import ( AFMRecord, AgentMetadata, ClientAuthentication, + HttpTransport, MCPServer, + StdioTransport, ToolFilter, Tools, - Transport, ) from afm_langchain.tools.mcp import ( ApiKeyAuth, @@ -60,7 +63,26 @@ def make_mcp_server( return MCPServer( name=name, - transport=Transport(type="http", url=url, authentication=auth), + transport=HttpTransport(type="http", url=url, authentication=auth), + tool_filter=tool_filter, + ) + + +def make_stdio_mcp_server( + name: str = "stdio-server", + command: str = "python", + args: list[str] | None = None, + env: dict[str, str] | None = None, + tool_filter: ToolFilter | None = None, +) -> MCPServer: + return MCPServer( + name=name, + transport=StdioTransport( + type="stdio", + command=command, + args=args, + env=env, + ), tool_filter=tool_filter, ) @@ -168,17 +190,19 @@ def test_from_mcp_server_creates_client(self): client = MCPClient.from_mcp_server(server) assert client.name == "test" - assert client.url == "http://localhost:8080/mcp" - assert client.authentication is None + assert isinstance(client.transport, HttpTransport) + assert client.transport.url == "http://localhost:8080/mcp" + assert client.transport.authentication is None assert client.tool_filter is None def test_from_mcp_server_with_auth(self): server = make_mcp_server(name="test", auth_type="bearer") client = MCPClient.from_mcp_server(server) - assert client.authentication is not None - assert client.authentication.type == "bearer" - assert client.authentication.token == "test-token" + assert isinstance(client.transport, HttpTransport) + assert client.transport.authentication is not None + assert client.transport.authentication.type == "bearer" + assert client.transport.authentication.token == "test-token" def test_from_mcp_server_with_tool_filter(self): tool_filter = ToolFilter(allow=["tool1", "tool2"]) @@ -188,23 +212,80 @@ def test_from_mcp_server_with_tool_filter(self): assert client.tool_filter is not None assert client.tool_filter.allow == ["tool1", "tool2"] - def test_from_mcp_server_unsupported_transport_raises_error(self): - server = MCPServer( - name="test", - transport=Transport(type="http", url="http://localhost:8080"), + def test_from_mcp_server_creates_stdio_client(self): + server = make_stdio_mcp_server( + name="stdio-test", + command="python", + args=["server.py"], ) - # Manually override the type to simulate invalid transport - object.__setattr__(server.transport, "type", "stdio") + client = MCPClient.from_mcp_server(server) + + assert client.name == "stdio-test" + assert isinstance(client.transport, StdioTransport) + assert client.transport.command == "python" + assert client.transport.args == ["server.py"] + assert client.tool_filter is None + + def test_build_connection_config_http(self): + server = make_mcp_server(name="test", url="http://localhost:8080/mcp") + client = MCPClient.from_mcp_server(server) + config = client._build_connection_config() + + assert config["transport"] == "streamable_http" + assert config["url"] == "http://localhost:8080/mcp" + assert "auth" not in config + + def test_build_connection_config_http_with_auth(self): + server = make_mcp_server(name="test", auth_type="bearer") + client = MCPClient.from_mcp_server(server) + config = client._build_connection_config() - with pytest.raises(MCPError, match="Unsupported transport type"): - MCPClient.from_mcp_server(server) + assert config["transport"] == "streamable_http" + assert "auth" in config + assert isinstance(config["auth"], BearerAuth) + + def test_build_connection_config_stdio(self): + server = make_stdio_mcp_server( + name="stdio-test", + command="npx", + args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], + ) + client = MCPClient.from_mcp_server(server) + config = cast(StdioConnection, client._build_connection_config()) + + assert config["transport"] == "stdio" + assert config["command"] == "npx" + assert config["args"] == [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/tmp", + ] + assert "env" not in config + + def test_build_connection_config_stdio_with_env(self): + server = make_stdio_mcp_server( + name="stdio-test", + command="python", + args=["server.py"], + env={"DB_PATH": "./data.db", "API_KEY": "secret"}, + ) + client = MCPClient.from_mcp_server(server) + config = cast(StdioConnection, client._build_connection_config()) + + assert config["transport"] == "stdio" + assert config["env"] == {"DB_PATH": "./data.db", "API_KEY": "secret"} + + def test_build_connection_config_stdio_no_args_defaults_to_empty_list(self): + server = make_stdio_mcp_server(name="stdio-test", command="python") + client = MCPClient.from_mcp_server(server) + config = cast(StdioConnection, client._build_connection_config()) + + assert config["args"] == [] @pytest.mark.asyncio async def test_get_tools_calls_mcp_client(self): - client = MCPClient( - name="test-server", - url="http://localhost:8080/mcp", - ) + server = make_mcp_server(name="test-server", url="http://localhost:8080/mcp") + client = MCPClient.from_mcp_server(server) mock_tools = [make_mock_tool("tool1"), make_mock_tool("tool2")] @@ -221,11 +302,12 @@ async def test_get_tools_calls_mcp_client(self): @pytest.mark.asyncio async def test_get_tools_applies_filtering(self): tool_filter = ToolFilter(allow=["tool1"]) - client = MCPClient( + server = make_mcp_server( name="test-server", url="http://localhost:8080/mcp", tool_filter=tool_filter, ) + client = MCPClient.from_mcp_server(server) mock_tools = [make_mock_tool("tool1"), make_mock_tool("tool2")] @@ -241,10 +323,8 @@ async def test_get_tools_applies_filtering(self): @pytest.mark.asyncio async def test_get_tools_connection_error_raises_mcp_error(self): - client = MCPClient( - name="test-server", - url="http://localhost:8080/mcp", - ) + server = make_mcp_server(name="test-server", url="http://localhost:8080/mcp") + client = MCPClient.from_mcp_server(server) with patch("afm_langchain.tools.mcp.MultiServerMCPClient") as MockClient: mock_instance = AsyncMock() @@ -254,6 +334,28 @@ async def test_get_tools_connection_error_raises_mcp_error(self): with pytest.raises(MCPConnectionError, match="Failed to connect"): await client.get_tools() + @pytest.mark.asyncio + async def test_get_tools_stdio_calls_mcp_client(self): + server = make_stdio_mcp_server( + name="stdio-server", + command="python", + args=["server.py"], + ) + client = MCPClient.from_mcp_server(server) + + mock_tools = [make_mock_tool("stdio_tool")] + + with patch("afm_langchain.tools.mcp.MultiServerMCPClient") as MockClient: + mock_instance = AsyncMock() + mock_instance.get_tools.return_value = mock_tools + MockClient.return_value = mock_instance + + result = await client.get_tools() + + assert len(result) == 1 + assert result[0].name == "stdio_tool" + mock_instance.get_tools.assert_called_once_with(server_name="stdio-server") + class TestMCPManager: def test_from_afm_with_no_tools_returns_none(self): @@ -433,3 +535,37 @@ async def test_get_tools_partial_failure_not_cached(self): assert tools[1].name == "tool2" mock_get1_retry.assert_called_once() mock_get2_retry.assert_called_once() + + @pytest.mark.asyncio + async def test_get_tools_mixed_http_and_stdio_servers(self): + servers = [ + make_mcp_server(name="http-server", url="http://localhost:8080/mcp"), + make_stdio_mcp_server( + name="stdio-server", + command="python", + args=["server.py"], + ), + ] + manager = MCPManager(servers) + + assert len(manager._clients) == 2 + assert isinstance(manager._clients[0].transport, HttpTransport) + assert isinstance(manager._clients[1].transport, StdioTransport) + + with ( + patch.object( + manager._clients[0], + "get_tools", + return_value=[make_mock_tool("http_tool")], + ), + patch.object( + manager._clients[1], + "get_tools", + return_value=[make_mock_tool("stdio_tool")], + ), + ): + tools = await manager.get_tools() + + assert len(tools) == 2 + assert tools[0].name == "http_tool" + assert tools[1].name == "stdio_tool" From 8747ee44259d9c42a98eeed3719b2180326c78a5 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 19 Feb 2026 16:21:45 +0530 Subject: [PATCH 03/22] Bump versions for stdio support --- python-interpreter/packages/afm-cli/pyproject.toml | 6 +++--- python-interpreter/packages/afm-core/pyproject.toml | 2 +- python-interpreter/packages/afm-langchain/pyproject.toml | 4 ++-- python-interpreter/uv.lock | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python-interpreter/packages/afm-cli/pyproject.toml b/python-interpreter/packages/afm-cli/pyproject.toml index 2b01765..c53dcc9 100644 --- a/python-interpreter/packages/afm-cli/pyproject.toml +++ b/python-interpreter/packages/afm-cli/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "afm-cli" -version = "0.2.10" +version = "0.3.0" description = "AFM CLI metapackage: installs afm-core and afm-langchain" readme = "README.md" classifiers = [ @@ -11,8 +11,8 @@ license = "Apache-2.0" requires-python = ">=3.11" urls = { Repository = "https://github.com/wso2/reference-implementations-afm" } dependencies = [ - "afm-core==0.1.7", - "afm-langchain>=0.1.0", + "afm-core==0.2.0", + "afm-langchain>=0.2.0", ] [project.scripts] diff --git a/python-interpreter/packages/afm-core/pyproject.toml b/python-interpreter/packages/afm-core/pyproject.toml index a191f37..545fa6f 100644 --- a/python-interpreter/packages/afm-core/pyproject.toml +++ b/python-interpreter/packages/afm-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "afm-core" -version = "0.1.7" +version = "0.2.0" description = "AFM (Agent-Flavored Markdown) core: parser, CLI, protocols, and interfaces" readme = "README.md" classifiers = [ diff --git a/python-interpreter/packages/afm-langchain/pyproject.toml b/python-interpreter/packages/afm-langchain/pyproject.toml index 21c7aa9..2cc545c 100644 --- a/python-interpreter/packages/afm-langchain/pyproject.toml +++ b/python-interpreter/packages/afm-langchain/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "afm-langchain" -version = "0.1.4" +version = "0.2.0" description = "AFM LangChain execution backend" readme = "README.md" classifiers = [ @@ -11,7 +11,7 @@ license = "Apache-2.0" requires-python = ">=3.11" urls = { Repository = "https://github.com/wso2/reference-implementations-afm" } dependencies = [ - "afm-core>=0.1.0,<0.2.0", + "afm-core>=0.2.0,<0.3.0", "langchain>=1.2.8", "langchain-openai>=1.1.7", "langchain-anthropic>=1.3.1", diff --git a/python-interpreter/uv.lock b/python-interpreter/uv.lock index f35fa46..2e846d7 100644 --- a/python-interpreter/uv.lock +++ b/python-interpreter/uv.lock @@ -26,7 +26,7 @@ dev = [ [[package]] name = "afm-cli" -version = "0.2.10" +version = "0.3.0" source = { editable = "packages/afm-cli" } dependencies = [ { name = "afm-core" }, @@ -41,7 +41,7 @@ requires-dist = [ [[package]] name = "afm-core" -version = "0.1.7" +version = "0.2.0" source = { editable = "packages/afm-core" } dependencies = [ { name = "click" }, @@ -74,7 +74,7 @@ requires-dist = [ [[package]] name = "afm-langchain" -version = "0.1.4" +version = "0.2.0" source = { editable = "packages/afm-langchain" } dependencies = [ { name = "afm-core" }, From d6df1f769ac9945ea27c2dfbcd32cc083abc1a21 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Tue, 24 Feb 2026 15:52:54 +0530 Subject: [PATCH 04/22] Add uv and npm to docker image for stdio mcp --- python-interpreter/Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/python-interpreter/Dockerfile b/python-interpreter/Dockerfile index cc16a09..dadf725 100644 --- a/python-interpreter/Dockerfile +++ b/python-interpreter/Dockerfile @@ -28,7 +28,22 @@ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-editable --package afm-core --package afm-langchain # Stage 2: Final image +# VARIANT=full installs Node.js, npm (npx), git, and uv/uvx for MCP server support. +# VARIANT=slim ships only Python + .venv. FROM python:3.13-alpine +ARG VARIANT=full + +RUN if [ "$VARIANT" = "full" ]; then \ + apk add --no-cache nodejs npm git && \ + echo "Full variant: nodejs, npm, git installed"; \ + fi + +# Install uv and uvx for Python-based MCP server support (full variant only) +COPY --from=ghcr.io/astral-sh/uv:0.10.0 /uv /uvx /uv-bins/ +RUN if [ "$VARIANT" = "full" ]; then \ + mv /uv-bins/uv /uv-bins/uvx /bin/; \ + fi && \ + rm -rf /uv-bins # Set working directory WORKDIR /app From ef6fcc3b282d5b8f1f98973f15761bf4285a2ecf Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Tue, 24 Feb 2026 15:53:39 +0530 Subject: [PATCH 05/22] Add a slim image without the additional deps --- .github/workflows/python-interpreter.yml | 22 ++++++++++++++- .github/workflows/release-docker.yml | 35 +++++++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-interpreter.yml b/.github/workflows/python-interpreter.yml index 9a50e28..d8401e1 100644 --- a/.github/workflows/python-interpreter.yml +++ b/.github/workflows/python-interpreter.yml @@ -73,12 +73,13 @@ jobs: OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') echo "image_name=ghcr.io/$OWNER_LOWER/afm-langchain-interpreter" >> $GITHUB_OUTPUT - - name: Build and push Docker image + - name: Build and push full image uses: docker/build-push-action@v5 with: context: python-interpreter push: true platforms: linux/amd64,linux/arm64 + build-args: VARIANT=full tags: | ${{ steps.meta.outputs.image_name }}:latest ${{ steps.meta.outputs.image_name }}:${{ github.sha }} @@ -90,3 +91,22 @@ jobs: annotations: | index:org.opencontainers.image.source=https://github.com/${{ github.repository }} index:org.opencontainers.image.licenses=Apache-2.0 + + - name: Build and push slim image + uses: docker/build-push-action@v5 + with: + context: python-interpreter + push: true + platforms: linux/amd64,linux/arm64 + build-args: VARIANT=slim + tags: | + ${{ steps.meta.outputs.image_name }}:slim + ${{ steps.meta.outputs.image_name }}:${{ github.sha }}-slim + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.title=AFM LangChain Interpreter (Slim) + org.opencontainers.image.licenses=Apache-2.0 + annotations: | + index:org.opencontainers.image.source=https://github.com/${{ github.repository }} + index:org.opencontainers.image.licenses=Apache-2.0 diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index 2749db8..20bc0bb 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -57,20 +57,23 @@ jobs: # GHCR requires lowercase repository names OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') FULL_IMAGE="ghcr.io/$OWNER_LOWER/$IMAGE_NAME" - TAGS="$FULL_IMAGE:v$VERSION" - if [ "$UPDATE_LATEST" = "true" ]; then - TAGS="$TAGS,$FULL_IMAGE:latest" - fi - echo "TAGS=$TAGS" >> $GITHUB_OUTPUT + TAGS_FULL="$FULL_IMAGE:v$VERSION" + [ "$UPDATE_LATEST" = "true" ] && TAGS_FULL="$TAGS_FULL,$FULL_IMAGE:latest" + echo "TAGS_FULL=$TAGS_FULL" >> $GITHUB_OUTPUT + + TAGS_SLIM="$FULL_IMAGE:v$VERSION-slim" + [ "$UPDATE_LATEST" = "true" ] && TAGS_SLIM="$TAGS_SLIM,$FULL_IMAGE:slim" + echo "TAGS_SLIM=$TAGS_SLIM" >> $GITHUB_OUTPUT echo "FULL_IMAGE=$FULL_IMAGE" >> $GITHUB_OUTPUT - - name: Build and push Docker image + - name: Build and push full image uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} push: true platforms: linux/amd64,linux/arm64 - tags: ${{ steps.docker-tags.outputs.TAGS }} + build-args: VARIANT=full + tags: ${{ steps.docker-tags.outputs.TAGS_FULL }} labels: | org.opencontainers.image.source=https://github.com/${{ github.repository }} org.opencontainers.image.version=${{ inputs.version }} @@ -81,6 +84,24 @@ jobs: index:org.opencontainers.image.source=https://github.com/${{ github.repository }} index:org.opencontainers.image.licenses=Apache-2.0 + - name: Build and push slim image + uses: docker/build-push-action@v5 + with: + context: ${{ inputs.context }} + push: true + platforms: linux/amd64,linux/arm64 + build-args: VARIANT=slim + tags: ${{ steps.docker-tags.outputs.TAGS_SLIM }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.version=${{ inputs.version }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.title=${{ inputs.image_title }} (Slim) + org.opencontainers.image.licenses=Apache-2.0 + annotations: | + index:org.opencontainers.image.source=https://github.com/${{ github.repository }} + index:org.opencontainers.image.licenses=Apache-2.0 + - name: Scan Docker image for vulnerabilities uses: aquasecurity/trivy-action@0.34.0 with: From e4d9f98665c7b2ec93301206c5de91d07b2b94e3 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 16:36:06 +0530 Subject: [PATCH 06/22] Add not yet supported error for stdio in Ballerina interpreter This makes it so that the ballerina implemntation does not fail at the parser level when trying to run an afm file with stdio transport mcp --- ballerina-interpreter/agent.bal | 5 ++--- ballerina-interpreter/parser.bal | 38 +++++++++++++++++++++++++++----- ballerina-interpreter/types.bal | 14 ++++++++++-- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/ballerina-interpreter/agent.bal b/ballerina-interpreter/agent.bal index d1147d1..bad401d 100644 --- a/ballerina-interpreter/agent.bal +++ b/ballerina-interpreter/agent.bal @@ -31,9 +31,8 @@ function createAgent(AFMRecord afmRecord) returns ai:Agent|error { if mcpServers is MCPServer[] { foreach MCPServer mcpConn in mcpServers { Transport transport = mcpConn.transport; - if transport.'type != "http" { - log:printWarn(string `Unsupported transport type: ${transport.'type}, only 'http' is supported`); - continue; + if transport is StdioTransport { + return error("Stdio transport is not yet supported by the Ballerina interpreter"); } string[]? filteredTools = getFilteredTools(mcpConn.tool_filter); diff --git a/ballerina-interpreter/parser.bal b/ballerina-interpreter/parser.bal index dcb3851..7a2a3ef 100644 --- a/ballerina-interpreter/parser.bal +++ b/ballerina-interpreter/parser.bal @@ -246,12 +246,38 @@ function validateHttpVariables(AFMRecord afmRecord) returns error? { } Transport transport = server.transport; - if containsHttpVariable(transport.url) { - erroredKeys.push("tools.mcp.transport.url"); - } - - if authenticationContainsHttpVariable(transport.authentication) { - erroredKeys.push("tools.mcp.transport.authentication"); + if transport is HttpTransport { + if containsHttpVariable(transport.url) { + erroredKeys.push("tools.mcp.transport.url"); + } + + if authenticationContainsHttpVariable(transport.authentication) { + erroredKeys.push("tools.mcp.transport.authentication"); + } + } else { + if containsHttpVariable(transport.command) { + erroredKeys.push("tools.mcp.transport.command"); + } + + string[]? args = transport.args; + if args is string[] { + foreach string arg in args { + if containsHttpVariable(arg) { + erroredKeys.push("tools.mcp.transport.args"); + break; + } + } + } + + map? env = transport.env; + if env is map { + foreach string val in env { + if containsHttpVariable(val) { + erroredKeys.push("tools.mcp.transport.env"); + break; + } + } + } } if toolFilterContainsHttpVariable(server.tool_filter) { diff --git a/ballerina-interpreter/types.bal b/ballerina-interpreter/types.bal index 4b0a90b..0ff5079 100644 --- a/ballerina-interpreter/types.bal +++ b/ballerina-interpreter/types.bal @@ -27,15 +27,25 @@ type Model record {| |}; enum TransportType { - http + http, + stdio } -type Transport record {| +type HttpTransport record {| http 'type = http; string url; ClientAuthentication authentication?; |}; +type StdioTransport record {| + stdio 'type = stdio; + string command; + string[] args?; + map env?; +|}; + +type Transport HttpTransport|StdioTransport; + type ClientAuthentication record { string 'type; }; From 3d2a9045f5edd0efbd4bb6390a70f7f6e219c9d7 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 17:37:05 +0530 Subject: [PATCH 07/22] Address review suggestions --- python-interpreter/Dockerfile | 1 - .../packages/afm-langchain/src/afm_langchain/tools/mcp.py | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/python-interpreter/Dockerfile b/python-interpreter/Dockerfile index dadf725..1102a96 100644 --- a/python-interpreter/Dockerfile +++ b/python-interpreter/Dockerfile @@ -35,7 +35,6 @@ ARG VARIANT=full RUN if [ "$VARIANT" = "full" ]; then \ apk add --no-cache nodejs npm git && \ - echo "Full variant: nodejs, npm, git installed"; \ fi # Install uv and uvx for Python-based MCP server support (full variant only) diff --git a/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py b/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py index 591fd42..709a065 100644 --- a/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py +++ b/python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py @@ -19,10 +19,6 @@ import logging import httpx -from langchain_core.tools import BaseTool -from langchain_mcp_adapters.client import MultiServerMCPClient -from langchain_mcp_adapters.sessions import StdioConnection, StreamableHttpConnection - from afm.exceptions import ( MCPAuthenticationError, MCPConnectionError, @@ -36,6 +32,9 @@ StdioTransport, ToolFilter, ) +from langchain_core.tools import BaseTool +from langchain_mcp_adapters.client import MultiServerMCPClient +from langchain_mcp_adapters.sessions import StdioConnection, StreamableHttpConnection logger = logging.getLogger(__name__) @@ -164,7 +163,6 @@ def _build_connection_config(self) -> StreamableHttpConnection | StdioConnection return config else: - # StdioTransport config: StdioConnection = { "transport": "stdio", "command": self.transport.command, From 9c6cb407daed10a3e2815a250861f4934dce4205 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 17:43:36 +0530 Subject: [PATCH 08/22] Add trivy scan for both docker images --- .github/workflows/release-docker.yml | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index 20bc0bb..b7fe3c4 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -102,18 +102,37 @@ jobs: index:org.opencontainers.image.source=https://github.com/${{ github.repository }} index:org.opencontainers.image.licenses=Apache-2.0 - - name: Scan Docker image for vulnerabilities + - name: Scan full Docker image for vulnerabilities uses: aquasecurity/trivy-action@0.34.0 with: image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }} format: "sarif" - output: "trivy-results.sarif" + output: "trivy-results-full.sarif" severity: "CRITICAL,HIGH" limit-severities-for-sarif: true exit-code: "1" - - name: Upload Trivy scan results to GitHub Security tab + - name: Upload full image Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v4 if: always() with: - sarif_file: "trivy-results.sarif" + sarif_file: "trivy-results-full.sarif" + category: "trivy-full" + + - name: Scan slim Docker image for vulnerabilities + if: always() + uses: aquasecurity/trivy-action@0.34.0 + with: + image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }}-slim + format: "sarif" + output: "trivy-results-slim.sarif" + severity: "CRITICAL,HIGH" + limit-severities-for-sarif: true + exit-code: "1" + + - name: Upload slim image Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v4 + if: always() + with: + sarif_file: "trivy-results-slim.sarif" + category: "trivy-slim" From 1f54865e9ab99e92ebc3e7e976acabfb7e9ea130 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 18:07:02 +0530 Subject: [PATCH 09/22] Skip slim docker image for release-ballerina Fix release-python workflow --- .github/workflows/release-docker.yml | 10 +++++++-- .github/workflows/release-python.yml | 33 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index b7fe3c4..c783a55 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -23,6 +23,11 @@ on: description: "Human-readable image title for OCI labels (e.g., AFM Ballerina Interpreter)" required: true type: string + build_slim: + description: "Whether to build and push a slim image variant" + required: false + default: false + type: boolean jobs: docker: @@ -85,6 +90,7 @@ jobs: index:org.opencontainers.image.licenses=Apache-2.0 - name: Build and push slim image + if: ${{ inputs.build_slim }} uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} @@ -120,7 +126,7 @@ jobs: category: "trivy-full" - name: Scan slim Docker image for vulnerabilities - if: always() + if: ${{ always() && inputs.build_slim }} uses: aquasecurity/trivy-action@0.34.0 with: image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }}-slim @@ -132,7 +138,7 @@ jobs: - name: Upload slim image Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v4 - if: always() + if: ${{ always() && inputs.build_slim }} with: sarif_file: "trivy-results-slim.sarif" category: "trivy-slim" diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index d6bb4db..9dad19b 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -4,16 +4,16 @@ on: workflow_dispatch: inputs: package: - description: 'Python package to release' + description: "Python package to release" required: true type: choice options: - afm-core - afm-langchain branch: - description: 'Branch to release from' + description: "Branch to release from" required: false - default: 'main' + default: "main" type: string concurrency: @@ -121,11 +121,11 @@ jobs: pypi-publish: needs: [validate, test, docker] if: >- - !cancelled() - && needs.validate.result == 'success' - && needs.test.result == 'success' - && (needs.docker.result == 'success' - || needs.docker.result == 'skipped') + !cancelled() + && needs.validate.result == 'success' + && needs.test.result == 'success' + && (needs.docker.result == 'success' + || needs.docker.result == 'skipped') runs-on: ubuntu-latest steps: - name: Checkout repository @@ -181,6 +181,7 @@ jobs: version: ${{ needs.validate.outputs.release_version }} branch: ${{ inputs.branch }} image_title: AFM LangChain Interpreter + build_slim: true permissions: packages: write security-events: write @@ -188,11 +189,11 @@ jobs: finalize: needs: [validate, pypi-publish, docker] if: >- - !cancelled() - && needs.validate.result == 'success' - && needs.pypi-publish.result == 'success' - && (needs.docker.result == 'success' - || needs.docker.result == 'skipped') + !cancelled() + && needs.validate.result == 'success' + && needs.pypi-publish.result == 'success' + && (needs.docker.result == 'success' + || needs.docker.result == 'skipped') uses: ./.github/workflows/release-finalize.yml with: tag: ${{ needs.validate.outputs.tag }} @@ -207,9 +208,9 @@ jobs: bump-version: needs: [validate, finalize] if: >- - !cancelled() - && needs.validate.result == 'success' - && needs.finalize.result == 'success' + !cancelled() + && needs.validate.result == 'success' + && needs.finalize.result == 'success' runs-on: ubuntu-latest permissions: contents: write From 0595c90b48c4b7b1eb50f02430fdfdf501749be0 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 18:09:01 +0530 Subject: [PATCH 10/22] Derive trivy category based on image name --- .github/workflows/release-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index c783a55..497e2dd 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -123,7 +123,7 @@ jobs: if: always() with: sarif_file: "trivy-results-full.sarif" - category: "trivy-full" + category: "trivy-full-${{ inputs.image_name }}" - name: Scan slim Docker image for vulnerabilities if: ${{ always() && inputs.build_slim }} @@ -141,4 +141,4 @@ jobs: if: ${{ always() && inputs.build_slim }} with: sarif_file: "trivy-results-slim.sarif" - category: "trivy-slim" + category: "trivy-slim-${{ inputs.image_name }}" From 09b8d89c17aa664ca6fb7001b1c191445b5e6460 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 18:31:08 +0530 Subject: [PATCH 11/22] Disable bump version step in release workflows Due to branch protection rules, the current version nump step in both ballerina and python release workflows fail. Disabled them for now. If a contributor forgets to bump the version the validate step will still fail. --- .github/workflows/release-ballerina.yml | 1 + .github/workflows/release-python.yml | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-ballerina.yml b/.github/workflows/release-ballerina.yml index 7129e02..f9080da 100644 --- a/.github/workflows/release-ballerina.yml +++ b/.github/workflows/release-ballerina.yml @@ -139,6 +139,7 @@ jobs: bump-version: needs: [validate, finalize] + if: false # Disabled: direct push blocked by branch protection runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 9dad19b..7f1e2db 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -207,10 +207,7 @@ jobs: bump-version: needs: [validate, finalize] - if: >- - !cancelled() - && needs.validate.result == 'success' - && needs.finalize.result == 'success' + if: false # Disabled: direct push blocked by branch protection runs-on: ubuntu-latest permissions: contents: write From 3797e6cb254a2844d611b89cecb6d1d1da167430 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 14:07:28 +0530 Subject: [PATCH 12/22] Add pypi publish skipping option --- .github/workflows/release-python.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 7f1e2db..8acb0d4 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -15,6 +15,11 @@ on: required: false default: "main" type: string + skip_pypi: + description: "Skip PyPI publishing" + required: false + default: false + type: boolean concurrency: group: release-python-interpreter @@ -122,6 +127,7 @@ jobs: needs: [validate, test, docker] if: >- !cancelled() + && !inputs.skip_pypi && needs.validate.result == 'success' && needs.test.result == 'success' && (needs.docker.result == 'success' @@ -191,7 +197,8 @@ jobs: if: >- !cancelled() && needs.validate.result == 'success' - && needs.pypi-publish.result == 'success' + && (needs.pypi-publish.result == 'success' + || needs.pypi-publish.result == 'skipped') && (needs.docker.result == 'success' || needs.docker.result == 'skipped') uses: ./.github/workflows/release-finalize.yml From edb26748f03ca30062761e18ba25c724cd41ee5f Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 18:55:29 +0530 Subject: [PATCH 13/22] Fix Dockerfile --- python-interpreter/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-interpreter/Dockerfile b/python-interpreter/Dockerfile index 1102a96..c2e99e7 100644 --- a/python-interpreter/Dockerfile +++ b/python-interpreter/Dockerfile @@ -34,7 +34,7 @@ FROM python:3.13-alpine ARG VARIANT=full RUN if [ "$VARIANT" = "full" ]; then \ - apk add --no-cache nodejs npm git && \ + apk add --no-cache nodejs npm git; \ fi # Install uv and uvx for Python-based MCP server support (full variant only) From 67b823a31596d8672ed1b8680ad2e7927dc8795f Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 19:01:06 +0530 Subject: [PATCH 14/22] Upgrade depdendencies --- python-interpreter/uv.lock | 424 ++++++++++++++++++------------------- 1 file changed, 212 insertions(+), 212 deletions(-) diff --git a/python-interpreter/uv.lock b/python-interpreter/uv.lock index 2e846d7..2e0be55 100644 --- a/python-interpreter/uv.lock +++ b/python-interpreter/uv.lock @@ -252,7 +252,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.79.0" +version = "0.84.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -264,9 +264,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/b1/91aea3f8fd180d01d133d931a167a78a3737b3fd39ccef2ae8d6619c24fd/anthropic-0.79.0.tar.gz", hash = "sha256:8707aafb3b1176ed6c13e2b1c9fb3efddce90d17aee5d8b83a86c70dcdcca871", size = 509825, upload-time = "2026-02-07T18:06:18.388Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/ea/0869d6df9ef83dcf393aeefc12dd81677d091c6ffc86f783e51cf44062f2/anthropic-0.84.0.tar.gz", hash = "sha256:72f5f90e5aebe62dca316cb013629cfa24996b0f5a4593b8c3d712bc03c43c37", size = 539457, upload-time = "2026-02-25T05:22:38.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/b2/cc0b8e874a18d7da50b0fda8c99e4ac123f23bf47b471827c5f6f3e4a767/anthropic-0.79.0-py3-none-any.whl", hash = "sha256:04cbd473b6bbda4ca2e41dd670fe2f829a911530f01697d0a1e37321eb75f3cf", size = 405918, upload-time = "2026-02-07T18:06:20.246Z" }, + { url = "https://files.pythonhosted.org/packages/64/ca/218fa25002a332c0aa149ba18ffc0543175998b1f65de63f6d106689a345/anthropic-0.84.0-py3-none-any.whl", hash = "sha256:861c4c50f91ca45f942e091d83b60530ad6d4f98733bfe648065364da05d29e7", size = 455156, upload-time = "2026-02-25T05:22:40.468Z" }, ] [[package]] @@ -305,11 +305,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -659,7 +659,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.129.0" +version = "0.133.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -668,9 +668,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/47/75f6bea02e797abff1bca968d5997793898032d9923c1935ae2efdece642/fastapi-0.129.0.tar.gz", hash = "sha256:61315cebd2e65df5f97ec298c888f9de30430dd0612d59d6480beafbc10655af", size = 375450, upload-time = "2026-02-12T13:54:52.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/04/ab382c7c03dd545f2c964d06e87ad0d5faa944a2434186ad9c285f5d87e0/fastapi-0.133.0.tar.gz", hash = "sha256:b900a2bf5685cdb0647a41d5900bdeafc3a9e8a28ac08c6246b76699e164d60d", size = 373265, upload-time = "2026-02-24T09:53:40.143Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/dd/d0ee25348ac58245ee9f90b6f3cbb666bf01f69be7e0911f9851bddbda16/fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec", size = 102950, upload-time = "2026-02-12T13:54:54.528Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/023e75a2ec3f5440e380df6caf4d28edc0806d007193e6fb0707237886a4/fastapi-0.133.0-py3-none-any.whl", hash = "sha256:0a78878483d60702a1dde864c24ab349a1a53ef4db6b6f74f8cd4a2b2bc67d2f", size = 104787, upload-time = "2026-02-24T09:53:41.404Z" }, ] [[package]] @@ -1003,21 +1003,21 @@ wheels = [ [[package]] name = "langchain-anthropic" -version = "1.3.3" +version = "1.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/48/cf217b3836099220737ff1f8fd07a554993080dfc9c0b4dd4af16ccb0604/langchain_anthropic-1.3.3.tar.gz", hash = "sha256:37198413c9bde5a9e9829f13c7b9ed4870d7085e7fba9fd803ef4d98ef8ea220", size = 686916, upload-time = "2026-02-10T21:02:28.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4e/7c1ffac126f5e62b0b9066f331f91ae69361e73476fd3ca1b19f8d8a3cc3/langchain_anthropic-1.3.4.tar.gz", hash = "sha256:000ed4c2d6fb8842b4ffeed22a74a3e84f9e9bcb63638e4abbb4a1d8ffa07211", size = 671858, upload-time = "2026-02-24T13:54:01.738Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/f1/cf56d47964b6fe080cdc54c3e32bc05e560927d549b2634b39d14aaf6e05/langchain_anthropic-1.3.3-py3-none-any.whl", hash = "sha256:8008ce5fb680268681673e09f93a9ac08eba9e304477101e5e138f06b5cd8710", size = 46831, upload-time = "2026-02-10T21:02:27.386Z" }, + { url = "https://files.pythonhosted.org/packages/9b/cf/b7c7b7270efbb3db2edbf14b09ba9110a41628f3a85a11cae9527a35641c/langchain_anthropic-1.3.4-py3-none-any.whl", hash = "sha256:cd112dcc8049aef09f58b3c4338b2c9db5ee98105e08664954a4e40d8bf120b9", size = 47454, upload-time = "2026-02-24T13:54:00.53Z" }, ] [[package]] name = "langchain-core" -version = "1.2.11" +version = "1.2.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1029,9 +1029,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/17/1943cedfc118e04b8128e4c3e1dbf0fa0ea58eefddbb6198cfd699d19f01/langchain_core-1.2.11.tar.gz", hash = "sha256:f164bb36602dd74a3a50c1334fca75309ad5ed95767acdfdbb9fa95ce28a1e01", size = 831211, upload-time = "2026-02-10T20:35:28.35Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/db/693d81b6c229aceb7c6e6809939c6ab1b023554227a25de438f00c0389c6/langchain_core-1.2.15.tar.gz", hash = "sha256:7d5f5d2daa8ddbe4054a96101dc5d509926f831b9914808c24640987d499758c", size = 835280, upload-time = "2026-02-23T15:04:49.185Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/30/1f80e3fc674353cad975ed5294353d42512535d2094ef032c06454c2c873/langchain_core-1.2.11-py3-none-any.whl", hash = "sha256:ae11ceb8dda60d0b9d09e763116e592f1683327c17be5b715f350fd29aee65d3", size = 500062, upload-time = "2026-02-10T20:35:26.698Z" }, + { url = "https://files.pythonhosted.org/packages/48/e0/a6a83dde94400b43d9b091ecbb41a50d6f86c4fecacb81b13d8452a7712b/langchain_core-1.2.15-py3-none-any.whl", hash = "sha256:8d920d8a31d8c223966a3993d8c79fd6093b9665f2222fc878812f3a52072ab7", size = 502213, upload-time = "2026-02-23T15:04:47.967Z" }, ] [[package]] @@ -1050,21 +1050,21 @@ wheels = [ [[package]] name = "langchain-openai" -version = "1.1.9" +version = "1.1.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/ae/1dbeb49ab8f098f78ec52e21627e705e5d7c684dc8826c2c34cc2746233a/langchain_openai-1.1.9.tar.gz", hash = "sha256:fdee25dcf4b0685d8e2f59856f4d5405431ef9e04ab53afe19e2e8360fed8234", size = 1004828, upload-time = "2026-02-10T21:03:21.615Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/0f/01147f842499338ae3b0dd0a351fb83006d9ed623cf3a999bd68ba5bbe2d/langchain_openai-1.1.10.tar.gz", hash = "sha256:ca6fae7cf19425acc81814efed59c7d205ec9a1f284fd1d08aae9bda85d6501b", size = 1059755, upload-time = "2026-02-17T18:03:44.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a1/8a20d19f69d022c10d34afa42d972cc50f971b880d0eb4a828cf3dd824a8/langchain_openai-1.1.9-py3-none-any.whl", hash = "sha256:ca2482b136c45fb67c0db84a9817de675e0eb8fb2203a33914c1b7a96f273940", size = 85769, upload-time = "2026-02-10T21:03:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/72/17/3785cbcdc81c451179247e4176d2697879cb4f45ab2c59d949ca574e072d/langchain_openai-1.1.10-py3-none-any.whl", hash = "sha256:d91b2c09e9fbc70f7af45345d3aa477744962d41c73a029beb46b4f83b824827", size = 87205, upload-time = "2026-02-17T18:03:43.502Z" }, ] [[package]] name = "langgraph" -version = "1.0.8" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1074,9 +1074,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/49/e9551965d8a44dd9afdc55cbcdc5a9bd18bee6918cc2395b225d40adb77c/langgraph-1.0.8.tar.gz", hash = "sha256:2630fc578846995114fd659f8b14df9eff5a4e78c49413f67718725e88ceb544", size = 498708, upload-time = "2026-02-06T12:31:13.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/63/69373a6721f30026ffa462a62084b11ed4bb5a201d1672366e13a89532f3/langgraph-1.0.9.tar.gz", hash = "sha256:feac2729faba7d3c325bef76f240d7d7f66b02d2cbf4fdb1ed7d0cc83f963651", size = 502800, upload-time = "2026-02-19T18:19:45.228Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/72/b0d7fc1007821a08dfc03ce232f39f209aa4aa46414ea3d125b24e35093a/langgraph-1.0.8-py3-none-any.whl", hash = "sha256:da737177c024caad7e5262642bece4f54edf4cba2c905a1d1338963f41cf0904", size = 158144, upload-time = "2026-02-06T12:31:12.489Z" }, + { url = "https://files.pythonhosted.org/packages/23/a2/562a6c2430085c2c29b23c1e1d12233bf41a64e9a9832eda7573af3666cf/langgraph-1.0.9-py3-none-any.whl", hash = "sha256:bce0d1f3e9a20434215a2a818395a58aedfc11c87bd6b52706c0db5c05ec44ec", size = 158150, upload-time = "2026-02-19T18:19:43.913Z" }, ] [[package]] @@ -1094,33 +1094,33 @@ wheels = [ [[package]] name = "langgraph-prebuilt" -version = "1.0.7" +version = "1.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/59/711aecd1a50999456850dc328f3cad72b4372d8218838d8d5326f80cb76f/langgraph_prebuilt-1.0.7.tar.gz", hash = "sha256:38e097e06de810de4d0e028ffc0e432bb56d1fb417620fb1dfdc76c5e03e4bf9", size = 163692, upload-time = "2026-01-22T16:45:22.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/49/5e37abb3f38a17a3487634abc2a5da87c208cc1d14577eb8d7184b25c886/langgraph_prebuilt-1.0.7-py3-none-any.whl", hash = "sha256:e14923516504405bb5edc3977085bc9622c35476b50c1808544490e13871fe7c", size = 35324, upload-time = "2026-01-22T16:45:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, ] [[package]] name = "langgraph-sdk" -version = "0.3.5" +version = "0.3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "orjson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/2b/2dae368ac76e315197f07ab58077aadf20833c226fbfd450d71745850314/langgraph_sdk-0.3.5.tar.gz", hash = "sha256:64669e9885a908578eed921ef9a8e52b8d0cd38db1e3e5d6d299d4e6f8830ac0", size = 177470, upload-time = "2026-02-10T16:56:09.18Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/bd/ca8ae5c6a34be6d4f7aa86016e010ff96b3a939456041565797952e3014d/langgraph_sdk-0.3.9.tar.gz", hash = "sha256:8be8958529b3f6d493ec248fdb46e539362efda75784654a42a7091d22504e0e", size = 184287, upload-time = "2026-02-24T18:39:03.276Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/d5/a14d957c515ba7a9713bf0f03f2b9277979c403bc50f829bdfd54ae7dc9e/langgraph_sdk-0.3.5-py3-none-any.whl", hash = "sha256:bcfa1dcbddadb604076ce46f5e08969538735e5ac47fa863d4fac5a512dab5c9", size = 70851, upload-time = "2026-02-10T16:56:07.983Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4c/7a7510260fbda788efd13bf4650d3e7d80988118441ac811ec78e0aa03ac/langgraph_sdk-0.3.9-py3-none-any.whl", hash = "sha256:94654294250c920789b6ed0d8a70c0117fed5736b61efc24ff647157359453c5", size = 90511, upload-time = "2026-02-24T18:39:02.012Z" }, ] [[package]] name = "langsmith" -version = "0.7.1" +version = "0.7.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -1133,9 +1133,9 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/48/3151de6df96e0977b8d319b03905e29db0df6929a85df1d922a030b7e68d/langsmith-0.7.1.tar.gz", hash = "sha256:e3fec2f97f7c5192f192f4873d6a076b8c6469768022323dded07087d8cb70a4", size = 984367, upload-time = "2026-02-10T01:55:24.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/b1/a1514b6fe33dc956bee1e6aba88470999d53a6ed02ec8fd14d6d409b8fb7/langsmith-0.7.6.tar.gz", hash = "sha256:e8646f8429d3c1641c7bae3c01bfdc3dfa27625994b0ef4303714d6b06fe1ef9", size = 1041741, upload-time = "2026-02-21T01:26:34.296Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/87/6f2b008a456b4f5fd0fb1509bb7e1e9368c1a0c9641a535f224a9ddc10f3/langsmith-0.7.1-py3-none-any.whl", hash = "sha256:92cfa54253d35417184c297ad25bfd921d95f15d60a1ca75f14d4e7acd152a29", size = 322515, upload-time = "2026-02-10T01:55:22.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/9865e5f0c49d74e3f4ea5697dadf11f2b9c9ae037f0bff599583ebe59189/langsmith-0.7.6-py3-none-any.whl", hash = "sha256:28d256584969db723b68189a7dbb065836572728ab4d9597ec5379fe0a1e1641", size = 325475, upload-time = "2026-02-21T01:26:32.504Z" }, ] [[package]] @@ -1459,7 +1459,7 @@ wheels = [ [[package]] name = "openai" -version = "2.20.0" +version = "2.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1471,9 +1471,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/5a/f495777c02625bfa18212b6e3b73f1893094f2bf660976eb4bc6f43a1ca2/openai-2.20.0.tar.gz", hash = "sha256:2654a689208cd0bf1098bb9462e8d722af5cbe961e6bba54e6f19fb843d88db1", size = 642355, upload-time = "2026-02-10T19:02:54.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/a0/cf4297aa51bbc21e83ef0ac018947fa06aea8f2364aad7c96cbf148590e6/openai-2.20.0-py3-none-any.whl", hash = "sha256:38d989c4b1075cd1f76abc68364059d822327cf1a932531d429795f4fc18be99", size = 1098479, upload-time = "2026-02-10T19:02:52.157Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, ] [[package]] @@ -1603,11 +1603,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.6.0" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/474d0a8508029286b905622e6929470fb84337cfa08f9d09fbb624515249/platformdirs-4.6.0.tar.gz", hash = "sha256:4a13c2db1071e5846c3b3e04e5b095c0de36b2a24be9a3bc0145ca66fce4e328", size = 23433, upload-time = "2026-02-12T14:36:21.288Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/10/1b0dcf51427326f70e50d98df21b18c228117a743a1fc515a42f8dc7d342/platformdirs-4.6.0-py3-none-any.whl", hash = "sha256:dd7f808d828e1764a22ebff09e60f175ee3c41876606a6132a688d809c7c9c73", size = 19549, upload-time = "2026-02-12T14:36:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -1841,16 +1841,16 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.12.0" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, ] [[package]] @@ -2027,106 +2027,106 @@ wheels = [ [[package]] name = "regex" -version = "2026.1.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/c9/0c80c96eab96948363d270143138d671d5731c3a692b417629bf3492a9d6/regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a", size = 488168, upload-time = "2026-01-14T23:14:16.129Z" }, - { url = "https://files.pythonhosted.org/packages/17/f0/271c92f5389a552494c429e5cc38d76d1322eb142fb5db3c8ccc47751468/regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f", size = 290636, upload-time = "2026-01-14T23:14:17.715Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f9/5f1fd077d106ca5655a0f9ff8f25a1ab55b92128b5713a91ed7134ff688e/regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1", size = 288496, upload-time = "2026-01-14T23:14:19.326Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e1/8f43b03a4968c748858ec77f746c286d81f896c2e437ccf050ebc5d3128c/regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b", size = 793503, upload-time = "2026-01-14T23:14:20.922Z" }, - { url = "https://files.pythonhosted.org/packages/8d/4e/a39a5e8edc5377a46a7c875c2f9a626ed3338cb3bb06931be461c3e1a34a/regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8", size = 860535, upload-time = "2026-01-14T23:14:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1c/9dce667a32a9477f7a2869c1c767dc00727284a9fa3ff5c09a5c6c03575e/regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413", size = 907225, upload-time = "2026-01-14T23:14:23.897Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3c/87ca0a02736d16b6262921425e84b48984e77d8e4e572c9072ce96e66c30/regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026", size = 800526, upload-time = "2026-01-14T23:14:26.039Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/647d5715aeea7c87bdcbd2f578f47b415f55c24e361e639fe8c0cc88878f/regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785", size = 773446, upload-time = "2026-01-14T23:14:28.109Z" }, - { url = "https://files.pythonhosted.org/packages/af/89/bf22cac25cb4ba0fe6bff52ebedbb65b77a179052a9d6037136ae93f42f4/regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e", size = 783051, upload-time = "2026-01-14T23:14:29.929Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f4/6ed03e71dca6348a5188363a34f5e26ffd5db1404780288ff0d79513bce4/regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763", size = 854485, upload-time = "2026-01-14T23:14:31.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9a/8e8560bd78caded8eb137e3e47612430a05b9a772caf60876435192d670a/regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb", size = 762195, upload-time = "2026-01-14T23:14:32.802Z" }, - { url = "https://files.pythonhosted.org/packages/38/6b/61fc710f9aa8dfcd764fe27d37edfaa023b1a23305a0d84fccd5adb346ea/regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2", size = 845986, upload-time = "2026-01-14T23:14:34.898Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2e/fbee4cb93f9d686901a7ca8d94285b80405e8c34fe4107f63ffcbfb56379/regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1", size = 788992, upload-time = "2026-01-14T23:14:37.116Z" }, - { url = "https://files.pythonhosted.org/packages/ed/14/3076348f3f586de64b1ab75a3fbabdaab7684af7f308ad43be7ef1849e55/regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569", size = 265893, upload-time = "2026-01-14T23:14:38.426Z" }, - { url = "https://files.pythonhosted.org/packages/0f/19/772cf8b5fc803f5c89ba85d8b1870a1ca580dc482aa030383a9289c82e44/regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7", size = 277840, upload-time = "2026-01-14T23:14:39.785Z" }, - { url = "https://files.pythonhosted.org/packages/78/84/d05f61142709474da3c0853222d91086d3e1372bcdab516c6fd8d80f3297/regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec", size = 270374, upload-time = "2026-01-14T23:14:41.592Z" }, - { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" }, - { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" }, - { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" }, - { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" }, - { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" }, - { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" }, - { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" }, - { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" }, - { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" }, - { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" }, - { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2e/6870bb16e982669b674cce3ee9ff2d1d46ab80528ee6bcc20fb2292efb60/regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e", size = 489164, upload-time = "2026-01-14T23:15:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/9774542e203849b0286badf67199970a44ebdb0cc5fb739f06e47ada72f8/regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10", size = 291218, upload-time = "2026-01-14T23:15:15.647Z" }, - { url = "https://files.pythonhosted.org/packages/b2/87/b0cda79f22b8dee05f774922a214da109f9a4c0eca5da2c9d72d77ea062c/regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc", size = 288895, upload-time = "2026-01-14T23:15:17.788Z" }, - { url = "https://files.pythonhosted.org/packages/3b/6a/0041f0a2170d32be01ab981d6346c83a8934277d82c780d60b127331f264/regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599", size = 798680, upload-time = "2026-01-14T23:15:19.342Z" }, - { url = "https://files.pythonhosted.org/packages/58/de/30e1cfcdbe3e891324aa7568b7c968771f82190df5524fabc1138cb2d45a/regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae", size = 864210, upload-time = "2026-01-14T23:15:22.005Z" }, - { url = "https://files.pythonhosted.org/packages/64/44/4db2f5c5ca0ccd40ff052ae7b1e9731352fcdad946c2b812285a7505ca75/regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5", size = 912358, upload-time = "2026-01-14T23:15:24.569Z" }, - { url = "https://files.pythonhosted.org/packages/79/b6/e6a5665d43a7c42467138c8a2549be432bad22cbd206f5ec87162de74bd7/regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6", size = 803583, upload-time = "2026-01-14T23:15:26.526Z" }, - { url = "https://files.pythonhosted.org/packages/e7/53/7cd478222169d85d74d7437e74750005e993f52f335f7c04ff7adfda3310/regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788", size = 775782, upload-time = "2026-01-14T23:15:29.352Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b5/75f9a9ee4b03a7c009fe60500fe550b45df94f0955ca29af16333ef557c5/regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714", size = 787978, upload-time = "2026-01-14T23:15:31.295Z" }, - { url = "https://files.pythonhosted.org/packages/72/b3/79821c826245bbe9ccbb54f6eadb7879c722fd3e0248c17bfc90bf54e123/regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d", size = 858550, upload-time = "2026-01-14T23:15:33.558Z" }, - { url = "https://files.pythonhosted.org/packages/4a/85/2ab5f77a1c465745bfbfcb3ad63178a58337ae8d5274315e2cc623a822fa/regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3", size = 763747, upload-time = "2026-01-14T23:15:35.206Z" }, - { url = "https://files.pythonhosted.org/packages/6d/84/c27df502d4bfe2873a3e3a7cf1bdb2b9cc10284d1a44797cf38bed790470/regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31", size = 850615, upload-time = "2026-01-14T23:15:37.523Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b7/658a9782fb253680aa8ecb5ccbb51f69e088ed48142c46d9f0c99b46c575/regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3", size = 789951, upload-time = "2026-01-14T23:15:39.582Z" }, - { url = "https://files.pythonhosted.org/packages/fc/2a/5928af114441e059f15b2f63e188bd00c6529b3051c974ade7444b85fcda/regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f", size = 266275, upload-time = "2026-01-14T23:15:42.108Z" }, - { url = "https://files.pythonhosted.org/packages/4f/16/5bfbb89e435897bff28cf0352a992ca719d9e55ebf8b629203c96b6ce4f7/regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e", size = 277145, upload-time = "2026-01-14T23:15:44.244Z" }, - { url = "https://files.pythonhosted.org/packages/56/c1/a09ff7392ef4233296e821aec5f78c51be5e91ffde0d163059e50fd75835/regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337", size = 270411, upload-time = "2026-01-14T23:15:45.858Z" }, - { url = "https://files.pythonhosted.org/packages/3c/38/0cfd5a78e5c6db00e6782fdae70458f89850ce95baa5e8694ab91d89744f/regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be", size = 492068, upload-time = "2026-01-14T23:15:47.616Z" }, - { url = "https://files.pythonhosted.org/packages/50/72/6c86acff16cb7c959c4355826bbf06aad670682d07c8f3998d9ef4fee7cd/regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8", size = 292756, upload-time = "2026-01-14T23:15:49.307Z" }, - { url = "https://files.pythonhosted.org/packages/4e/58/df7fb69eadfe76526ddfce28abdc0af09ffe65f20c2c90932e89d705153f/regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd", size = 291114, upload-time = "2026-01-14T23:15:51.484Z" }, - { url = "https://files.pythonhosted.org/packages/ed/6c/a4011cd1cf96b90d2cdc7e156f91efbd26531e822a7fbb82a43c1016678e/regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a", size = 807524, upload-time = "2026-01-14T23:15:53.102Z" }, - { url = "https://files.pythonhosted.org/packages/1d/25/a53ffb73183f69c3e9f4355c4922b76d2840aee160af6af5fac229b6201d/regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93", size = 873455, upload-time = "2026-01-14T23:15:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/66/0b/8b47fc2e8f97d9b4a851736f3890a5f786443aa8901061c55f24c955f45b/regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af", size = 915007, upload-time = "2026-01-14T23:15:57.041Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/97de0d681e6d26fabe71968dbee06dd52819e9a22fdce5dac7256c31ed84/regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09", size = 812794, upload-time = "2026-01-14T23:15:58.916Z" }, - { url = "https://files.pythonhosted.org/packages/22/38/e752f94e860d429654aa2b1c51880bff8dfe8f084268258adf9151cf1f53/regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5", size = 781159, upload-time = "2026-01-14T23:16:00.817Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a7/d739ffaef33c378fc888302a018d7f81080393d96c476b058b8c64fd2b0d/regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794", size = 795558, upload-time = "2026-01-14T23:16:03.267Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c4/542876f9a0ac576100fc73e9c75b779f5c31e3527576cfc9cb3009dcc58a/regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a", size = 868427, upload-time = "2026-01-14T23:16:05.646Z" }, - { url = "https://files.pythonhosted.org/packages/fc/0f/d5655bea5b22069e32ae85a947aa564912f23758e112cdb74212848a1a1b/regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80", size = 769939, upload-time = "2026-01-14T23:16:07.542Z" }, - { url = "https://files.pythonhosted.org/packages/20/06/7e18a4fa9d326daeda46d471a44ef94201c46eaa26dbbb780b5d92cbfdda/regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2", size = 854753, upload-time = "2026-01-14T23:16:10.395Z" }, - { url = "https://files.pythonhosted.org/packages/3b/67/dc8946ef3965e166f558ef3b47f492bc364e96a265eb4a2bb3ca765c8e46/regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60", size = 799559, upload-time = "2026-01-14T23:16:12.347Z" }, - { url = "https://files.pythonhosted.org/packages/a5/61/1bba81ff6d50c86c65d9fd84ce9699dd106438ee4cdb105bf60374ee8412/regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952", size = 268879, upload-time = "2026-01-14T23:16:14.049Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5e/cef7d4c5fb0ea3ac5c775fd37db5747f7378b29526cc83f572198924ff47/regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10", size = 280317, upload-time = "2026-01-14T23:16:15.718Z" }, - { url = "https://files.pythonhosted.org/packages/b4/52/4317f7a5988544e34ab57b4bde0f04944c4786128c933fb09825924d3e82/regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829", size = 271551, upload-time = "2026-01-14T23:16:17.533Z" }, - { url = "https://files.pythonhosted.org/packages/52/0a/47fa888ec7cbbc7d62c5f2a6a888878e76169170ead271a35239edd8f0e8/regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac", size = 489170, upload-time = "2026-01-14T23:16:19.835Z" }, - { url = "https://files.pythonhosted.org/packages/ac/c4/d000e9b7296c15737c9301708e9e7fbdea009f8e93541b6b43bdb8219646/regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6", size = 291146, upload-time = "2026-01-14T23:16:21.541Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b6/921cc61982e538682bdf3bdf5b2c6ab6b34368da1f8e98a6c1ddc503c9cf/regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2", size = 288986, upload-time = "2026-01-14T23:16:23.381Z" }, - { url = "https://files.pythonhosted.org/packages/ca/33/eb7383dde0bbc93f4fb9d03453aab97e18ad4024ac7e26cef8d1f0a2cff0/regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846", size = 799098, upload-time = "2026-01-14T23:16:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/b664dccae898fc8d8b4c23accd853f723bde0f026c747b6f6262b688029c/regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b", size = 864980, upload-time = "2026-01-14T23:16:27.297Z" }, - { url = "https://files.pythonhosted.org/packages/16/40/0999e064a170eddd237bae9ccfcd8f28b3aa98a38bf727a086425542a4fc/regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e", size = 911607, upload-time = "2026-01-14T23:16:29.235Z" }, - { url = "https://files.pythonhosted.org/packages/07/78/c77f644b68ab054e5a674fb4da40ff7bffb2c88df58afa82dbf86573092d/regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde", size = 803358, upload-time = "2026-01-14T23:16:31.369Z" }, - { url = "https://files.pythonhosted.org/packages/27/31/d4292ea8566eaa551fafc07797961c5963cf5235c797cc2ae19b85dfd04d/regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5", size = 775833, upload-time = "2026-01-14T23:16:33.141Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b2/cff3bf2fea4133aa6fb0d1e370b37544d18c8350a2fa118c7e11d1db0e14/regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34", size = 788045, upload-time = "2026-01-14T23:16:35.005Z" }, - { url = "https://files.pythonhosted.org/packages/8d/99/2cb9b69045372ec877b6f5124bda4eb4253bc58b8fe5848c973f752bc52c/regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75", size = 859374, upload-time = "2026-01-14T23:16:36.919Z" }, - { url = "https://files.pythonhosted.org/packages/09/16/710b0a5abe8e077b1729a562d2f297224ad079f3a66dce46844c193416c8/regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e", size = 763940, upload-time = "2026-01-14T23:16:38.685Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/7585c8e744e40eb3d32f119191969b91de04c073fca98ec14299041f6e7e/regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160", size = 850112, upload-time = "2026-01-14T23:16:40.646Z" }, - { url = "https://files.pythonhosted.org/packages/af/d6/43e1dd85df86c49a347aa57c1f69d12c652c7b60e37ec162e3096194a278/regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1", size = 789586, upload-time = "2026-01-14T23:16:42.799Z" }, - { url = "https://files.pythonhosted.org/packages/93/38/77142422f631e013f316aaae83234c629555729a9fbc952b8a63ac91462a/regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1", size = 271691, upload-time = "2026-01-14T23:16:44.671Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a9/ab16b4649524ca9e05213c1cdbb7faa85cc2aa90a0230d2f796cbaf22736/regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903", size = 280422, upload-time = "2026-01-14T23:16:46.607Z" }, - { url = "https://files.pythonhosted.org/packages/be/2a/20fd057bf3521cb4791f69f869635f73e0aaf2b9ad2d260f728144f9047c/regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705", size = 273467, upload-time = "2026-01-14T23:16:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/ad/77/0b1e81857060b92b9cad239104c46507dd481b3ff1fa79f8e7f865aae38a/regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8", size = 492073, upload-time = "2026-01-14T23:16:51.154Z" }, - { url = "https://files.pythonhosted.org/packages/70/f3/f8302b0c208b22c1e4f423147e1913fd475ddd6230565b299925353de644/regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf", size = 292757, upload-time = "2026-01-14T23:16:53.08Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f0/ef55de2460f3b4a6da9d9e7daacd0cb79d4ef75c64a2af316e68447f0df0/regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d", size = 291122, upload-time = "2026-01-14T23:16:55.383Z" }, - { url = "https://files.pythonhosted.org/packages/cf/55/bb8ccbacabbc3a11d863ee62a9f18b160a83084ea95cdfc5d207bfc3dd75/regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84", size = 807761, upload-time = "2026-01-14T23:16:57.251Z" }, - { url = "https://files.pythonhosted.org/packages/8f/84/f75d937f17f81e55679a0509e86176e29caa7298c38bd1db7ce9c0bf6075/regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df", size = 873538, upload-time = "2026-01-14T23:16:59.349Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d9/0da86327df70349aa8d86390da91171bd3ca4f0e7c1d1d453a9c10344da3/regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434", size = 915066, upload-time = "2026-01-14T23:17:01.607Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5e/f660fb23fc77baa2a61aa1f1fe3a4eea2bbb8a286ddec148030672e18834/regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a", size = 812938, upload-time = "2026-01-14T23:17:04.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/33/a47a29bfecebbbfd1e5cd3f26b28020a97e4820f1c5148e66e3b7d4b4992/regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10", size = 781314, upload-time = "2026-01-14T23:17:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/65/ec/7ec2bbfd4c3f4e494a24dec4c6943a668e2030426b1b8b949a6462d2c17b/regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac", size = 795652, upload-time = "2026-01-14T23:17:08.521Z" }, - { url = "https://files.pythonhosted.org/packages/46/79/a5d8651ae131fe27d7c521ad300aa7f1c7be1dbeee4d446498af5411b8a9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea", size = 868550, upload-time = "2026-01-14T23:17:10.573Z" }, - { url = "https://files.pythonhosted.org/packages/06/b7/25635d2809664b79f183070786a5552dd4e627e5aedb0065f4e3cf8ee37d/regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e", size = 769981, upload-time = "2026-01-14T23:17:12.871Z" }, - { url = "https://files.pythonhosted.org/packages/16/8b/fc3fcbb2393dcfa4a6c5ffad92dc498e842df4581ea9d14309fcd3c55fb9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521", size = 854780, upload-time = "2026-01-14T23:17:14.837Z" }, - { url = "https://files.pythonhosted.org/packages/d0/38/dde117c76c624713c8a2842530be9c93ca8b606c0f6102d86e8cd1ce8bea/regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db", size = 799778, upload-time = "2026-01-14T23:17:17.369Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" }, - { url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" }, +version = "2026.2.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/c0/d8079d4f6342e4cec5c3e7d7415b5cd3e633d5f4124f7a4626908dbe84c7/regex-2026.2.19.tar.gz", hash = "sha256:6fb8cb09b10e38f3ae17cc6dc04a1df77762bd0351b6ba9041438e7cc85ec310", size = 414973, upload-time = "2026-02-19T19:03:47.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/93/43f405a98f54cc59c786efb4fc0b644615ed2392fc89d57d30da11f35b5b/regex-2026.2.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:93b16a18cadb938f0f2306267161d57eb33081a861cee9ffcd71e60941eb5dfc", size = 488365, upload-time = "2026-02-19T19:00:17.857Z" }, + { url = "https://files.pythonhosted.org/packages/66/46/da0efce22cd8f5ae28eeb25ac69703f49edcad3331ac22440776f4ea0867/regex-2026.2.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78af1e499cab704131f6f4e2f155b7f54ce396ca2acb6ef21a49507e4752e0be", size = 290737, upload-time = "2026-02-19T19:00:19.869Z" }, + { url = "https://files.pythonhosted.org/packages/fb/19/f735078448132c1c974974d30d5306337bc297fe6b6f126164bff72c1019/regex-2026.2.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eb20c11aa4c3793c9ad04c19a972078cdadb261b8429380364be28e867a843f2", size = 288654, upload-time = "2026-02-19T19:00:21.307Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/6d7c24a2f423c03ad03e3fbddefa431057186ac1c4cb4fa98b03c7f39808/regex-2026.2.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db5fd91eec71e7b08de10011a2223d0faa20448d4e1380b9daa179fa7bf58906", size = 793785, upload-time = "2026-02-19T19:00:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/fdb8107504b3122a79bde6705ac1f9d495ed1fe35b87d7cfc1864471999a/regex-2026.2.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fdbade8acba71bb45057c2b72f477f0b527c4895f9c83e6cfc30d4a006c21726", size = 860731, upload-time = "2026-02-19T19:00:25.196Z" }, + { url = "https://files.pythonhosted.org/packages/9a/fd/cc8c6f05868defd840be6e75919b1c3f462357969ac2c2a0958363b4dc23/regex-2026.2.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:31a5f561eb111d6aae14202e7043fb0b406d3c8dddbbb9e60851725c9b38ab1d", size = 907350, upload-time = "2026-02-19T19:00:27.093Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1b/4590db9caa8db3d5a3fe31197c4e42c15aab3643b549ef6a454525fa3a61/regex-2026.2.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4584a3ee5f257b71e4b693cc9be3a5104249399f4116fe518c3f79b0c6fc7083", size = 800628, upload-time = "2026-02-19T19:00:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/76/05/513eaa5b96fa579fd0b813e19ec047baaaf573d7374ff010fa139b384bf7/regex-2026.2.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:196553ba2a2f47904e5dc272d948a746352e2644005627467e055be19d73b39e", size = 773711, upload-time = "2026-02-19T19:00:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/95/65/5aed06d8c54563d37fea496cf888be504879a3981a7c8e12c24b2c92c209/regex-2026.2.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0c10869d18abb759a3317c757746cc913d6324ce128b8bcec99350df10419f18", size = 783186, upload-time = "2026-02-19T19:00:34.598Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/79a633ad90f2371b4ef9cd72ba3a69a1a67d0cfaab4fe6fa8586d46044ef/regex-2026.2.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e689fed279cbe797a6b570bd18ff535b284d057202692c73420cb93cca41aa32", size = 854854, upload-time = "2026-02-19T19:00:37.306Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2d/0f113d477d9e91ec4545ec36c82e58be25038d06788229c91ad52da2b7f5/regex-2026.2.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0782bd983f19ac7594039c9277cd6f75c89598c1d72f417e4d30d874105eb0c7", size = 762279, upload-time = "2026-02-19T19:00:39.793Z" }, + { url = "https://files.pythonhosted.org/packages/39/cb/237e9fa4f61469fd4f037164dbe8e675a376c88cf73aaaa0aedfd305601c/regex-2026.2.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:dbb240c81cfed5d4a67cb86d7676d9f7ec9c3f186310bec37d8a1415210e111e", size = 846172, upload-time = "2026-02-19T19:00:42.134Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7c/104779c5915cc4eb557a33590f8a3f68089269c64287dd769afd76c7ce61/regex-2026.2.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80d31c3f1fe7e4c6cd1831cd4478a0609903044dfcdc4660abfe6fb307add7f0", size = 789078, upload-time = "2026-02-19T19:00:43.908Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4a/eae4e88b1317fb2ff57794915e0099198f51e760f6280b320adfa0ad396d/regex-2026.2.19-cp311-cp311-win32.whl", hash = "sha256:66e6a43225ff1064f8926adbafe0922b370d381c3330edaf9891cade52daa790", size = 266013, upload-time = "2026-02-19T19:00:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/ba89eb8fae79705e07ad1bd69e568f776159d2a8093c9dbc5303ee618298/regex-2026.2.19-cp311-cp311-win_amd64.whl", hash = "sha256:59a7a5216485a1896c5800e9feb8ff9213e11967b482633b6195d7da11450013", size = 277906, upload-time = "2026-02-19T19:00:49.011Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1a/042d8f04b28e318df92df69d8becb0f42221eb3dd4fe5e976522f4337c76/regex-2026.2.19-cp311-cp311-win_arm64.whl", hash = "sha256:ec661807ffc14c8d14bb0b8c1bb3d5906e476bc96f98b565b709d03962ee4dd4", size = 270463, upload-time = "2026-02-19T19:00:50.988Z" }, + { url = "https://files.pythonhosted.org/packages/b3/73/13b39c7c9356f333e564ab4790b6cb0df125b8e64e8d6474e73da49b1955/regex-2026.2.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c1665138776e4ac1aa75146669236f7a8a696433ec4e525abf092ca9189247cc", size = 489541, upload-time = "2026-02-19T19:00:52.728Z" }, + { url = "https://files.pythonhosted.org/packages/15/77/fcc7bd9a67000d07fbcc11ed226077287a40d5c84544e62171d29d3ef59c/regex-2026.2.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d792b84709021945597e05656aac059526df4e0c9ef60a0eaebb306f8fafcaa8", size = 291414, upload-time = "2026-02-19T19:00:54.51Z" }, + { url = "https://files.pythonhosted.org/packages/f9/87/3997fc72dc59233426ef2e18dfdd105bb123812fff740ee9cc348f1a3243/regex-2026.2.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db970bcce4d63b37b3f9eb8c893f0db980bbf1d404a1d8d2b17aa8189de92c53", size = 289140, upload-time = "2026-02-19T19:00:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d0/b7dd3883ed1cff8ee0c0c9462d828aaf12be63bf5dc55453cbf423523b13/regex-2026.2.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03d706fbe7dfec503c8c3cb76f9352b3e3b53b623672aa49f18a251a6c71b8e6", size = 798767, upload-time = "2026-02-19T19:00:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7e/8e2d09103832891b2b735a2515abf377db21144c6dd5ede1fb03c619bf09/regex-2026.2.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dbff048c042beef60aa1848961384572c5afb9e8b290b0f1203a5c42cf5af65", size = 864436, upload-time = "2026-02-19T19:01:00.772Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2e/afea8d23a6db1f67f45e3a0da3057104ce32e154f57dd0c8997274d45fcd/regex-2026.2.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccaaf9b907ea6b4223d5cbf5fa5dff5f33dc66f4907a25b967b8a81339a6e332", size = 912391, upload-time = "2026-02-19T19:01:02.865Z" }, + { url = "https://files.pythonhosted.org/packages/59/3c/ea5a4687adaba5e125b9bd6190153d0037325a0ba3757cc1537cc2c8dd90/regex-2026.2.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75472631eee7898e16a8a20998d15106cb31cfde21cdf96ab40b432a7082af06", size = 803702, upload-time = "2026-02-19T19:01:05.298Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c5/624a0705e8473a26488ec1a3a4e0b8763ecfc682a185c302dfec71daea35/regex-2026.2.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d89f85a5ccc0cec125c24be75610d433d65295827ebaf0d884cbe56df82d4774", size = 775980, upload-time = "2026-02-19T19:01:07.047Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/ed776642533232b5599b7c1f9d817fe11faf597e8a92b7a44b841daaae76/regex-2026.2.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9f81806abdca3234c3dd582b8a97492e93de3602c8772013cb4affa12d1668", size = 788122, upload-time = "2026-02-19T19:01:08.744Z" }, + { url = "https://files.pythonhosted.org/packages/8c/58/e93e093921d13b9784b4f69896b6e2a9e09580a265c59d9eb95e87d288f2/regex-2026.2.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9dadc10d1c2bbb1326e572a226d2ec56474ab8aab26fdb8cf19419b372c349a9", size = 858910, upload-time = "2026-02-19T19:01:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/85/77/ff1d25a0c56cd546e0455cbc93235beb33474899690e6a361fa6b52d265b/regex-2026.2.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6bc25d7e15f80c9dc7853cbb490b91c1ec7310808b09d56bd278fe03d776f4f6", size = 764153, upload-time = "2026-02-19T19:01:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ef/8ec58df26d52d04443b1dc56f9be4b409f43ed5ae6c0248a287f52311fc4/regex-2026.2.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:965d59792f5037d9138da6fed50ba943162160443b43d4895b182551805aff9c", size = 850348, upload-time = "2026-02-19T19:01:14.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b3/c42fd5ed91639ce5a4225b9df909180fc95586db071f2bf7c68d2ccbfbe6/regex-2026.2.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:38d88c6ed4a09ed61403dbdf515d969ccba34669af3961ceb7311ecd0cef504a", size = 789977, upload-time = "2026-02-19T19:01:15.838Z" }, + { url = "https://files.pythonhosted.org/packages/b6/22/bc3b58ebddbfd6ca5633e71fd41829ee931963aad1ebeec55aad0c23044e/regex-2026.2.19-cp312-cp312-win32.whl", hash = "sha256:5df947cabab4b643d4791af5e28aecf6bf62e6160e525651a12eba3d03755e6b", size = 266381, upload-time = "2026-02-19T19:01:17.952Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4a/6ff550b63e67603ee60e69dc6bd2d5694e85046a558f663b2434bdaeb285/regex-2026.2.19-cp312-cp312-win_amd64.whl", hash = "sha256:4146dc576ea99634ae9c15587d0c43273b4023a10702998edf0fa68ccb60237a", size = 277274, upload-time = "2026-02-19T19:01:19.826Z" }, + { url = "https://files.pythonhosted.org/packages/cc/29/9ec48b679b1e87e7bc8517dff45351eab38f74fbbda1fbcf0e9e6d4e8174/regex-2026.2.19-cp312-cp312-win_arm64.whl", hash = "sha256:cdc0a80f679353bd68450d2a42996090c30b2e15ca90ded6156c31f1a3b63f3b", size = 270509, upload-time = "2026-02-19T19:01:22.075Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2d/a849835e76ac88fcf9e8784e642d3ea635d183c4112150ca91499d6703af/regex-2026.2.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8df08decd339e8b3f6a2eb5c05c687fe9d963ae91f352bc57beb05f5b2ac6879", size = 489329, upload-time = "2026-02-19T19:01:23.841Z" }, + { url = "https://files.pythonhosted.org/packages/da/aa/78ff4666d3855490bae87845a5983485e765e1f970da20adffa2937b241d/regex-2026.2.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3aa0944f1dc6e92f91f3b306ba7f851e1009398c84bfd370633182ee4fc26a64", size = 291308, upload-time = "2026-02-19T19:01:25.605Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/714384efcc07ae6beba528a541f6e99188c5cc1bc0295337f4e8a868296d/regex-2026.2.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c13228fbecb03eadbfd8f521732c5fda09ef761af02e920a3148e18ad0e09968", size = 289033, upload-time = "2026-02-19T19:01:27.243Z" }, + { url = "https://files.pythonhosted.org/packages/75/ec/6438a9344d2869cf5265236a06af1ca6d885e5848b6561e10629bc8e5a11/regex-2026.2.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d0e72703c60d68b18b27cde7cdb65ed2570ae29fb37231aa3076bfb6b1d1c13", size = 798798, upload-time = "2026-02-19T19:01:28.877Z" }, + { url = "https://files.pythonhosted.org/packages/c2/be/b1ce2d395e3fd2ce5f2fde2522f76cade4297cfe84cd61990ff48308749c/regex-2026.2.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46e69a4bf552e30e74a8aa73f473c87efcb7f6e8c8ece60d9fd7bf13d5c86f02", size = 864444, upload-time = "2026-02-19T19:01:30.933Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/a3406460c504f7136f140d9461960c25f058b0240e4424d6fb73c7a067ab/regex-2026.2.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8edda06079bd770f7f0cf7f3bba1a0b447b96b4a543c91fe0c142d034c166161", size = 912633, upload-time = "2026-02-19T19:01:32.744Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d9/e5dbef95008d84e9af1dc0faabbc34a7fbc8daa05bc5807c5cf86c2bec49/regex-2026.2.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cbc69eae834afbf634f7c902fc72ff3e993f1c699156dd1af1adab5d06b7fe7", size = 803718, upload-time = "2026-02-19T19:01:34.61Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e5/61d80132690a1ef8dc48e0f44248036877aebf94235d43f63a20d1598888/regex-2026.2.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bcf57d30659996ee5c7937999874504c11b5a068edc9515e6a59221cc2744dd1", size = 775975, upload-time = "2026-02-19T19:01:36.525Z" }, + { url = "https://files.pythonhosted.org/packages/05/32/ae828b3b312c972cf228b634447de27237d593d61505e6ad84723f8eabba/regex-2026.2.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8e6e77cd92216eb489e21e5652a11b186afe9bdefca8a2db739fd6b205a9e0a4", size = 788129, upload-time = "2026-02-19T19:01:38.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/25/d74f34676f22bec401eddf0e5e457296941e10cbb2a49a571ca7a2c16e5a/regex-2026.2.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b9ab8dec42afefa6314ea9b31b188259ffdd93f433d77cad454cd0b8d235ce1c", size = 858818, upload-time = "2026-02-19T19:01:40.409Z" }, + { url = "https://files.pythonhosted.org/packages/1e/eb/0bc2b01a6b0b264e1406e5ef11cae3f634c3bd1a6e61206fd3227ce8e89c/regex-2026.2.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:294c0fb2e87c6bcc5f577c8f609210f5700b993151913352ed6c6af42f30f95f", size = 764186, upload-time = "2026-02-19T19:01:43.009Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/5fe5a630d0d99ecf0c3570f8905dafbc160443a2d80181607770086c9812/regex-2026.2.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c0924c64b082d4512b923ac016d6e1dcf647a3560b8a4c7e55cbbd13656cb4ed", size = 850363, upload-time = "2026-02-19T19:01:45.015Z" }, + { url = "https://files.pythonhosted.org/packages/c3/45/ef68d805294b01ec030cfd388724ba76a5a21a67f32af05b17924520cb0b/regex-2026.2.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:790dbf87b0361606cb0d79b393c3e8f4436a14ee56568a7463014565d97da02a", size = 790026, upload-time = "2026-02-19T19:01:47.51Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/40d3b66923dfc5aeba182f194f0ca35d09afe8c031a193e6ae46971a0a0e/regex-2026.2.19-cp313-cp313-win32.whl", hash = "sha256:43cdde87006271be6963896ed816733b10967baaf0e271d529c82e93da66675b", size = 266372, upload-time = "2026-02-19T19:01:49.469Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f2/39082e8739bfd553497689e74f9d5e5bb531d6f8936d0b94f43e18f219c0/regex-2026.2.19-cp313-cp313-win_amd64.whl", hash = "sha256:127ea69273485348a126ebbf3d6052604d3c7da284f797bba781f364c0947d47", size = 277253, upload-time = "2026-02-19T19:01:51.208Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c2/852b9600d53fb47e47080c203e2cdc0ac7e84e37032a57e0eaa37446033a/regex-2026.2.19-cp313-cp313-win_arm64.whl", hash = "sha256:5e56c669535ac59cbf96ca1ece0ef26cb66809990cda4fa45e1e32c3b146599e", size = 270505, upload-time = "2026-02-19T19:01:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a2/e0b4575b93bc84db3b1fab24183e008691cd2db5c0ef14ed52681fbd94dd/regex-2026.2.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93d881cab5afdc41a005dba1524a40947d6f7a525057aa64aaf16065cf62faa9", size = 492202, upload-time = "2026-02-19T19:01:54.816Z" }, + { url = "https://files.pythonhosted.org/packages/24/b5/b84fec8cbb5f92a7eed2b6b5353a6a9eed9670fee31817c2da9eb85dc797/regex-2026.2.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:80caaa1ddcc942ec7be18427354f9d58a79cee82dea2a6b3d4fd83302e1240d7", size = 292884, upload-time = "2026-02-19T19:01:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/70/0c/fe89966dfae43da46f475362401f03e4d7dc3a3c955b54f632abc52669e0/regex-2026.2.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d793c5b4d2b4c668524cd1651404cfc798d40694c759aec997e196fe9729ec60", size = 291236, upload-time = "2026-02-19T19:01:59.966Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f7/bda2695134f3e63eb5cccbbf608c2a12aab93d261ff4e2fe49b47fabc948/regex-2026.2.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5100acb20648d9efd3f4e7e91f51187f95f22a741dcd719548a6cf4e1b34b3f", size = 807660, upload-time = "2026-02-19T19:02:01.632Z" }, + { url = "https://files.pythonhosted.org/packages/11/56/6e3a4bf5e60d17326b7003d91bbde8938e439256dec211d835597a44972d/regex-2026.2.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5e3a31e94d10e52a896adaa3adf3621bd526ad2b45b8c2d23d1bbe74c7423007", size = 873585, upload-time = "2026-02-19T19:02:03.522Z" }, + { url = "https://files.pythonhosted.org/packages/35/5e/c90c6aa4d1317cc11839359479cfdd2662608f339e84e81ba751c8a4e461/regex-2026.2.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8497421099b981f67c99eba4154cf0dfd8e47159431427a11cfb6487f7791d9e", size = 915243, upload-time = "2026-02-19T19:02:05.608Z" }, + { url = "https://files.pythonhosted.org/packages/90/7c/981ea0694116793001496aaf9524e5c99e122ec3952d9e7f1878af3a6bf1/regex-2026.2.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e7a08622f7d51d7a068f7e4052a38739c412a3e74f55817073d2e2418149619", size = 812922, upload-time = "2026-02-19T19:02:08.115Z" }, + { url = "https://files.pythonhosted.org/packages/2d/be/9eda82afa425370ffdb3fa9f3ea42450b9ae4da3ff0a4ec20466f69e371b/regex-2026.2.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8abe671cf0f15c26b1ad389bf4043b068ce7d3b1c5d9313e12895f57d6738555", size = 781318, upload-time = "2026-02-19T19:02:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d5/50f0bbe56a8199f60a7b6c714e06e54b76b33d31806a69d0703b23ce2a9e/regex-2026.2.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5a8f28dd32a4ce9c41758d43b5b9115c1c497b4b1f50c457602c1d571fa98ce1", size = 795649, upload-time = "2026-02-19T19:02:11.96Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/d039f081e44a8b0134d0bb2dd805b0ddf390b69d0b58297ae098847c572f/regex-2026.2.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:654dc41a5ba9b8cc8432b3f1aa8906d8b45f3e9502442a07c2f27f6c63f85db5", size = 868844, upload-time = "2026-02-19T19:02:14.043Z" }, + { url = "https://files.pythonhosted.org/packages/ef/53/e2903b79a19ec8557fe7cd21cd093956ff2dbc2e0e33969e3adbe5b184dd/regex-2026.2.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4a02faea614e7fdd6ba8b3bec6c8e79529d356b100381cec76e638f45d12ca04", size = 770113, upload-time = "2026-02-19T19:02:16.161Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e2/784667767b55714ebb4e59bf106362327476b882c0b2f93c25e84cc99b1a/regex-2026.2.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d96162140bb819814428800934c7b71b7bffe81fb6da2d6abc1dcca31741eca3", size = 854922, upload-time = "2026-02-19T19:02:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/9ef4356bd4aed752775bd18071034979b85f035fec51f3a4f9dea497a254/regex-2026.2.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c227f2922153ee42bbeb355fd6d009f8c81d9d7bdd666e2276ce41f53ed9a743", size = 799636, upload-time = "2026-02-19T19:02:20.04Z" }, + { url = "https://files.pythonhosted.org/packages/cf/54/fcfc9287f20c5c9bd8db755aafe3e8cf4d99a6a3f1c7162ee182e0ca9374/regex-2026.2.19-cp313-cp313t-win32.whl", hash = "sha256:a178df8ec03011153fbcd2c70cb961bc98cbbd9694b28f706c318bee8927c3db", size = 268968, upload-time = "2026-02-19T19:02:22.816Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a0/ff24c6cb1273e42472706d277147fc38e1f9074a280fb6034b0fc9b69415/regex-2026.2.19-cp313-cp313t-win_amd64.whl", hash = "sha256:2c1693ca6f444d554aa246b592355b5cec030ace5a2729eae1b04ab6e853e768", size = 280390, upload-time = "2026-02-19T19:02:25.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/a3f6ad89d780ffdeebb4d5e2e3e30bd2ef1f70f6a94d1760e03dd1e12c60/regex-2026.2.19-cp313-cp313t-win_arm64.whl", hash = "sha256:c0761d7ae8d65773e01515ebb0b304df1bf37a0a79546caad9cbe79a42c12af7", size = 271643, upload-time = "2026-02-19T19:02:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e2/7ad4e76a6dddefc0d64dbe12a4d3ca3947a19ddc501f864a5df2a8222ddd/regex-2026.2.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:03d191a9bcf94d31af56d2575210cb0d0c6a054dbcad2ea9e00aa4c42903b919", size = 489306, upload-time = "2026-02-19T19:02:29.058Z" }, + { url = "https://files.pythonhosted.org/packages/14/95/ee1736135733afbcf1846c58671046f99c4d5170102a150ebb3dd8d701d9/regex-2026.2.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:516ee067c6c721d0d0bfb80a2004edbd060fffd07e456d4e1669e38fe82f922e", size = 291218, upload-time = "2026-02-19T19:02:31.083Z" }, + { url = "https://files.pythonhosted.org/packages/ef/08/180d1826c3d7065200a5168c6b993a44947395c7bb6e04b2c2a219c34225/regex-2026.2.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:997862c619994c4a356cb7c3592502cbd50c2ab98da5f61c5c871f10f22de7e5", size = 289097, upload-time = "2026-02-19T19:02:33.485Z" }, + { url = "https://files.pythonhosted.org/packages/28/93/0651924c390c5740f5f896723f8ddd946a6c63083a7d8647231c343912ff/regex-2026.2.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b9e1b8a7ebe2807cd7bbdf662510c8e43053a23262b9f46ad4fc2dfc9d204e", size = 799147, upload-time = "2026-02-19T19:02:35.669Z" }, + { url = "https://files.pythonhosted.org/packages/a7/00/2078bd8bcd37d58a756989adbfd9f1d0151b7ca4085a9c2a07e917fbac61/regex-2026.2.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6c8fb3b19652e425ff24169dad3ee07f99afa7996caa9dfbb3a9106cd726f49a", size = 865239, upload-time = "2026-02-19T19:02:38.012Z" }, + { url = "https://files.pythonhosted.org/packages/2a/13/75195161ec16936b35a365fa8c1dd2ab29fd910dd2587765062b174d8cfc/regex-2026.2.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50f1ee9488dd7a9fda850ec7c68cad7a32fa49fd19733f5403a3f92b451dcf73", size = 911904, upload-time = "2026-02-19T19:02:40.737Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/ac42f6012179343d1c4bd0ffee8c948d841cb32ea188d37e96d80527fcc9/regex-2026.2.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab780092b1424d13200aa5a62996e95f65ee3db8509be366437439cdc0af1a9f", size = 803518, upload-time = "2026-02-19T19:02:42.923Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d1/75a08e2269b007b9783f0f86aa64488e023141219cb5f14dc1e69cda56c6/regex-2026.2.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:17648e1a88e72d88641b12635e70e6c71c5136ba14edba29bf8fc6834005a265", size = 775866, upload-time = "2026-02-19T19:02:45.189Z" }, + { url = "https://files.pythonhosted.org/packages/92/41/70e7d05faf6994c2ca7a9fcaa536da8f8e4031d45b0ec04b57040ede201f/regex-2026.2.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f914ae8c804c8a8a562fe216100bc156bfb51338c1f8d55fe32cf407774359a", size = 788224, upload-time = "2026-02-19T19:02:47.804Z" }, + { url = "https://files.pythonhosted.org/packages/c8/83/34a2dd601f9deb13c20545c674a55f4a05c90869ab73d985b74d639bac43/regex-2026.2.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c7e121a918bbee3f12ac300ce0a0d2f2c979cf208fb071ed8df5a6323281915c", size = 859682, upload-time = "2026-02-19T19:02:50.583Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/136db9a09a7f222d6e48b806f3730e7af6499a8cad9c72ac0d49d52c746e/regex-2026.2.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2fedd459c791da24914ecc474feecd94cf7845efb262ac3134fe27cbd7eda799", size = 764223, upload-time = "2026-02-19T19:02:52.777Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/bb947743c78a16df481fa0635c50aa1a439bb80b0e6dc24cd4e49c716679/regex-2026.2.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ea8dfc99689240e61fb21b5fc2828f68b90abf7777d057b62d3166b7c1543c4c", size = 850101, upload-time = "2026-02-19T19:02:55.87Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/e3bfe6e97a99f7393665926be02fef772da7f8aa59e50bc3134e4262a032/regex-2026.2.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fff45852160960f29e184ec8a5be5ab4063cfd0b168d439d1fc4ac3744bf29e", size = 789904, upload-time = "2026-02-19T19:02:58.523Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/7e2be6f00cea59d08761b027ad237002e90cac74b1607200ebaa2ba3d586/regex-2026.2.19-cp314-cp314-win32.whl", hash = "sha256:5390b130cce14a7d1db226a3896273b7b35be10af35e69f1cca843b6e5d2bb2d", size = 271784, upload-time = "2026-02-19T19:03:00.418Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f6/639911530335773e7ec60bcaa519557b719586024c1d7eaad1daf87b646b/regex-2026.2.19-cp314-cp314-win_amd64.whl", hash = "sha256:e581f75d5c0b15669139ca1c2d3e23a65bb90e3c06ba9d9ea194c377c726a904", size = 280506, upload-time = "2026-02-19T19:03:02.302Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ec/2582b56b4e036d46bb9b5d74a18548439ffa16c11cf59076419174d80f48/regex-2026.2.19-cp314-cp314-win_arm64.whl", hash = "sha256:7187fdee1be0896c1499a991e9bf7c78e4b56b7863e7405d7bb687888ac10c4b", size = 273557, upload-time = "2026-02-19T19:03:04.836Z" }, + { url = "https://files.pythonhosted.org/packages/49/0b/f901cfeb4efd83e4f5c3e9f91a6de77e8e5ceb18555698aca3a27e215ed3/regex-2026.2.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5ec1d7c080832fdd4e150c6f5621fe674c70c63b3ae5a4454cebd7796263b175", size = 492196, upload-time = "2026-02-19T19:03:08.188Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/349b959e3da874e15eda853755567b4cde7e5309dbb1e07bfe910cfde452/regex-2026.2.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8457c1bc10ee9b29cdfd897ccda41dce6bde0e9abd514bcfef7bcd05e254d411", size = 292878, upload-time = "2026-02-19T19:03:10.272Z" }, + { url = "https://files.pythonhosted.org/packages/98/b0/9d81b3c2c5ddff428f8c506713737278979a2c476f6e3675a9c51da0c389/regex-2026.2.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cce8027010d1ffa3eb89a0b19621cdc78ae548ea2b49fea1f7bfb3ea77064c2b", size = 291235, upload-time = "2026-02-19T19:03:12.5Z" }, + { url = "https://files.pythonhosted.org/packages/04/e7/be7818df8691dbe9508c381ea2cc4c1153e4fdb1c4b06388abeaa93bd712/regex-2026.2.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11c138febb40546ff9e026dbbc41dc9fb8b29e61013fa5848ccfe045f5b23b83", size = 807893, upload-time = "2026-02-19T19:03:15.064Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b6/b898a8b983190cfa0276031c17beb73cfd1db07c03c8c37f606d80b655e2/regex-2026.2.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:74ff212aa61532246bb3036b3dfea62233414b0154b8bc3676975da78383cac3", size = 873696, upload-time = "2026-02-19T19:03:17.848Z" }, + { url = "https://files.pythonhosted.org/packages/1a/98/126ba671d54f19080ec87cad228fb4f3cc387fff8c4a01cb4e93f4ff9d94/regex-2026.2.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d00c95a2b6bfeb3ea1cb68d1751b1dfce2b05adc2a72c488d77a780db06ab867", size = 915493, upload-time = "2026-02-19T19:03:20.343Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/550c84a1a1a7371867fe8be2bea7df55e797cbca4709974811410e195c5d/regex-2026.2.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:311fcccb76af31be4c588d5a17f8f1a059ae8f4b097192896ebffc95612f223a", size = 813094, upload-time = "2026-02-19T19:03:23.287Z" }, + { url = "https://files.pythonhosted.org/packages/29/fb/ba221d2fc76a27b6b7d7a60f73a7a6a7bac21c6ba95616a08be2bcb434b0/regex-2026.2.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77cfd6b5e7c4e8bf7a39d243ea05882acf5e3c7002b0ef4756de6606893b0ecd", size = 781583, upload-time = "2026-02-19T19:03:26.872Z" }, + { url = "https://files.pythonhosted.org/packages/26/f1/af79231301297c9e962679efc04a31361b58dc62dec1fc0cb4b8dd95956a/regex-2026.2.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6380f29ff212ec922b6efb56100c089251940e0526a0d05aa7c2d9b571ddf2fe", size = 795875, upload-time = "2026-02-19T19:03:29.223Z" }, + { url = "https://files.pythonhosted.org/packages/a0/90/1e1d76cb0a2d0a4f38a039993e1c5cd971ae50435d751c5bae4f10e1c302/regex-2026.2.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:655f553a1fa3ab8a7fd570eca793408b8d26a80bfd89ed24d116baaf13a38969", size = 868916, upload-time = "2026-02-19T19:03:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/a1c01da76dbcfed690855a284c665cc0a370e7d02d1bd635cf9ff7dd74b8/regex-2026.2.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:015088b8558502f1f0bccd58754835aa154a7a5b0bd9d4c9b7b96ff4ae9ba876", size = 770386, upload-time = "2026-02-19T19:03:33.972Z" }, + { url = "https://files.pythonhosted.org/packages/49/6f/94842bf294f432ff3836bfd91032e2ecabea6d284227f12d1f935318c9c4/regex-2026.2.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9e6693b8567a59459b5dda19104c4a4dbbd4a1c78833eacc758796f2cfef1854", size = 855007, upload-time = "2026-02-19T19:03:36.238Z" }, + { url = "https://files.pythonhosted.org/packages/ff/93/393cd203ca0d1d368f05ce12d2c7e91a324bc93c240db2e6d5ada05835f4/regex-2026.2.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4071209fd4376ab5ceec72ad3507e9d3517c59e38a889079b98916477a871868", size = 799863, upload-time = "2026-02-19T19:03:38.497Z" }, + { url = "https://files.pythonhosted.org/packages/43/d9/35afda99bd92bf1a5831e55a4936d37ea4bed6e34c176a3c2238317faf4f/regex-2026.2.19-cp314-cp314t-win32.whl", hash = "sha256:2905ff4a97fad42f2d0834d8b1ea3c2f856ec209837e458d71a061a7d05f9f01", size = 274742, upload-time = "2026-02-19T19:03:40.804Z" }, + { url = "https://files.pythonhosted.org/packages/ae/42/7edc3344dcc87b698e9755f7f685d463852d481302539dae07135202d3ca/regex-2026.2.19-cp314-cp314t-win_amd64.whl", hash = "sha256:64128549b600987e0f335c2365879895f860a9161f283b14207c800a6ed623d3", size = 284443, upload-time = "2026-02-19T19:03:42.954Z" }, + { url = "https://files.pythonhosted.org/packages/3a/45/affdf2d851b42adf3d13fc5b3b059372e9bd299371fd84cf5723c45871fa/regex-2026.2.19-cp314-cp314t-win_arm64.whl", hash = "sha256:a09ae430e94c049dc6957f6baa35ee3418a3a77f3c12b6e02883bd80a2b679b0", size = 274932, upload-time = "2026-02-19T19:03:45.488Z" }, ] [[package]] @@ -2158,15 +2158,15 @@ wheels = [ [[package]] name = "rich" -version = "14.3.2" +version = "14.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, ] [[package]] @@ -2279,27 +2279,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, - { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, - { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, - { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, - { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, - { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, - { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, - { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, + { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, + { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, ] [[package]] @@ -2348,7 +2348,7 @@ wheels = [ [[package]] name = "textual" -version = "7.5.0" +version = "8.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", extra = ["linkify"] }, @@ -2358,9 +2358,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/38/7d169a765993efde5095c70a668bf4f5831bb7ac099e932f2783e9b71abf/textual-7.5.0.tar.gz", hash = "sha256:c730cba1e3d704e8f1ca915b6a3af01451e3bca380114baacf6abf87e9dac8b6", size = 1592319, upload-time = "2026-01-30T13:46:39.881Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/1e1f705825359590ddfaeda57653bd518c4ff7a96bb2c3239ba1b6fc4c51/textual-8.0.0.tar.gz", hash = "sha256:ce48f83a3d686c0fac0e80bf9136e1f8851c653aa6a4502e43293a151df18809", size = 1595895, upload-time = "2026-02-16T17:12:14.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/78/96ddb99933e11d91bc6e05edae23d2687e44213066bcbaca338898c73c47/textual-7.5.0-py3-none-any.whl", hash = "sha256:849dfee9d705eab3b2d07b33152b7bd74fb1f5056e002873cc448bce500c6374", size = 718164, upload-time = "2026-01-30T13:46:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/d3/be/e191c2a15da20530fde03564564e3e4b4220eb9d687d4014957e5c6a5e85/textual-8.0.0-py3-none-any.whl", hash = "sha256:8908f4ebe93a6b4f77ca7262197784a52162bc88b05f4ecf50ac93a92d49bb8f", size = 718904, upload-time = "2026-02-16T17:12:11.962Z" }, ] [[package]] @@ -2518,26 +2518,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.16" +version = "0.0.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/18/77f84d89db54ea0d1d1b09fa2f630ac4c240c8e270761cb908c06b6e735c/ty-0.0.16.tar.gz", hash = "sha256:a999b0db6aed7d6294d036ebe43301105681e0c821a19989be7c145805d7351c", size = 5129637, upload-time = "2026-02-10T20:24:16.48Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/15/9682700d8d60fdca7afa4febc83a2354b29cdcd56e66e19c92b521db3b39/ty-0.0.18.tar.gz", hash = "sha256:04ab7c3db5dcbcdac6ce62e48940d3a0124f377c05499d3f3e004e264ae94b83", size = 5214774, upload-time = "2026-02-20T21:51:31.173Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/b9/909ebcc7f59eaf8a2c18fb54bfcf1c106f99afb3e5460058d4b46dec7b20/ty-0.0.16-py3-none-linux_armv6l.whl", hash = "sha256:6d8833b86396ed742f2b34028f51c0e98dbf010b13ae4b79d1126749dc9dab15", size = 10113870, upload-time = "2026-02-10T20:24:11.864Z" }, - { url = "https://files.pythonhosted.org/packages/c3/2c/b963204f3df2fdbf46a4a1ea4a060af9bb676e065d59c70ad0f5ae0dbae8/ty-0.0.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934c0055d3b7f1cf3c8eab78c6c127ef7f347ff00443cef69614bda6f1502377", size = 9936286, upload-time = "2026-02-10T20:24:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/ef/4d/3d78294f2ddfdded231e94453dea0e0adef212b2bd6536296039164c2a3e/ty-0.0.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b55e8e8733b416d914003cd22e831e139f034681b05afed7e951cc1a5ea1b8d4", size = 9442660, upload-time = "2026-02-10T20:24:02.704Z" }, - { url = "https://files.pythonhosted.org/packages/15/40/ce48c0541e3b5749b0890725870769904e6b043e077d4710e5325d5cf807/ty-0.0.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feccae8f4abd6657de111353bd604f36e164844466346eb81ffee2c2b06ea0f0", size = 9934506, upload-time = "2026-02-10T20:24:35.818Z" }, - { url = "https://files.pythonhosted.org/packages/84/16/3b29de57e1ec6e56f50a4bb625ee0923edb058c5f53e29014873573a00cd/ty-0.0.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cad5e29d8765b92db5fa284940ac57149561f3f89470b363b9aab8a6ce553b0", size = 9933099, upload-time = "2026-02-10T20:24:43.003Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a1/e546995c25563d318c502b2f42af0fdbed91e1fc343708241e2076373644/ty-0.0.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86f28797c7dc06f081238270b533bf4fc8e93852f34df49fb660e0b58a5cda9a", size = 10438370, upload-time = "2026-02-10T20:24:33.44Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/22d301a4b2cce0f75ae84d07a495f87da193bcb68e096d43695a815c4708/ty-0.0.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be971a3b42bcae44d0e5787f88156ed2102ad07558c05a5ae4bfd32a99118e66", size = 10992160, upload-time = "2026-02-10T20:24:25.574Z" }, - { url = "https://files.pythonhosted.org/packages/6f/40/f1892b8c890db3f39a1bab8ec459b572de2df49e76d3cad2a9a239adcde9/ty-0.0.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c9f982b7c4250eb91af66933f436b3a2363c24b6353e94992eab6551166c8b7", size = 10717892, upload-time = "2026-02-10T20:24:05.914Z" }, - { url = "https://files.pythonhosted.org/packages/2f/1b/caf9be8d0c738983845f503f2e92ea64b8d5fae1dd5ca98c3fca4aa7dadc/ty-0.0.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d122edf85ce7bdf6f85d19158c991d858fc835677bd31ca46319c4913043dc84", size = 10510916, upload-time = "2026-02-10T20:24:00.252Z" }, - { url = "https://files.pythonhosted.org/packages/60/ea/28980f5c7e1f4c9c44995811ea6a36f2fcb205232a6ae0f5b60b11504621/ty-0.0.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:497ebdddbb0e35c7758ded5aa4c6245e8696a69d531d5c9b0c1a28a075374241", size = 9908506, upload-time = "2026-02-10T20:24:28.133Z" }, - { url = "https://files.pythonhosted.org/packages/f7/80/8672306596349463c21644554f935ff8720679a14fd658fef658f66da944/ty-0.0.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e1e0ac0837bde634b030243aeba8499383c0487e08f22e80f5abdacb5b0bd8ce", size = 9949486, upload-time = "2026-02-10T20:24:18.62Z" }, - { url = "https://files.pythonhosted.org/packages/8b/8a/d8747d36f30bd82ea157835f5b70d084c9bb5d52dd9491dba8a149792d6a/ty-0.0.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1216c9bcca551d9f89f47a817ebc80e88ac37683d71504e5509a6445f24fd024", size = 10145269, upload-time = "2026-02-10T20:24:38.249Z" }, - { url = "https://files.pythonhosted.org/packages/6f/4c/753535acc7243570c259158b7df67e9c9dd7dab9a21ee110baa4cdcec45d/ty-0.0.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:221bbdd2c6ee558452c96916ab67fcc465b86967cf0482e19571d18f9c831828", size = 10608644, upload-time = "2026-02-10T20:24:40.565Z" }, - { url = "https://files.pythonhosted.org/packages/3e/05/8e8db64cf45a8b16757e907f7a3bfde8d6203e4769b11b64e28d5bdcd79a/ty-0.0.16-py3-none-win32.whl", hash = "sha256:d52c4eb786be878e7514cab637200af607216fcc5539a06d26573ea496b26512", size = 9582579, upload-time = "2026-02-10T20:24:30.406Z" }, - { url = "https://files.pythonhosted.org/packages/25/bc/45759faea132cd1b2a9ff8374e42ba03d39d076594fbb94f3e0e2c226c62/ty-0.0.16-py3-none-win_amd64.whl", hash = "sha256:f572c216aa8ecf79e86589c6e6d4bebc01f1f3cb3be765c0febd942013e1e73a", size = 10436043, upload-time = "2026-02-10T20:23:57.51Z" }, - { url = "https://files.pythonhosted.org/packages/7f/02/70a491802e7593e444137ed4e41a04c34d186eb2856f452dd76b60f2e325/ty-0.0.16-py3-none-win_arm64.whl", hash = "sha256:430eadeb1c0de0c31ef7bef9d002bdbb5f25a31e3aad546f1714d76cd8da0a87", size = 9915122, upload-time = "2026-02-10T20:24:14.285Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/920460d4c22ea68fcdeb0b2fb53ea2aeb9c6d7875bde9278d84f2ac767b6/ty-0.0.18-py3-none-linux_armv6l.whl", hash = "sha256:4e5e91b0a79857316ef893c5068afc4b9872f9d257627d9bc8ac4d2715750d88", size = 10280825, upload-time = "2026-02-20T21:51:25.03Z" }, + { url = "https://files.pythonhosted.org/packages/83/56/62587de582d3d20d78fcdddd0594a73822ac5a399a12ef512085eb7a4de6/ty-0.0.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee0e578b3f8416e2d5416da9553b78fd33857868aa1384cb7fefeceee5ff102d", size = 10118324, upload-time = "2026-02-20T21:51:22.27Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2d/dbdace8d432a0755a7417f659bfd5b8a4261938ecbdfd7b42f4c454f5aa9/ty-0.0.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3f7a0487d36b939546a91d141f7fc3dbea32fab4982f618d5b04dc9d5b6da21e", size = 9605861, upload-time = "2026-02-20T21:51:16.066Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d9/de11c0280f778d5fc571393aada7fe9b8bc1dd6a738f2e2c45702b8b3150/ty-0.0.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5e2fa8d45f57ca487a470e4bf66319c09b561150e98ae2a6b1a97ef04c1a4eb", size = 10092701, upload-time = "2026-02-20T21:51:26.862Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/068d4d591d791041732171e7b63c37a54494b2e7d28e88d2167eaa9ad875/ty-0.0.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d75652e9e937f7044b1aca16091193e7ef11dac1c7ec952b7fb8292b7ba1f5f2", size = 10109203, upload-time = "2026-02-20T21:51:11.59Z" }, + { url = "https://files.pythonhosted.org/packages/34/e4/526a4aa56dc0ca2569aaa16880a1ab105c3b416dd70e87e25a05688999f3/ty-0.0.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:563c868edceb8f6ddd5e91113c17d3676b028f0ed380bdb3829b06d9beb90e58", size = 10614200, upload-time = "2026-02-20T21:51:20.298Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b68ab20a34122a395880922587fbfc3adf090d22e0fb546d4d20fe8c2621/ty-0.0.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:502e2a1f948bec563a0454fc25b074bf5cf041744adba8794d024277e151d3b0", size = 11153232, upload-time = "2026-02-20T21:51:14.121Z" }, + { url = "https://files.pythonhosted.org/packages/68/ea/678243c042343fcda7e6af36036c18676c355878dcdcd517639586d2cf9e/ty-0.0.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc881dea97021a3aa29134a476937fd8054775c4177d01b94db27fcfb7aab65b", size = 10832934, upload-time = "2026-02-20T21:51:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bd/7f8d647cef8b7b346c0163230a37e903c7461c7248574840b977045c77df/ty-0.0.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:421fcc3bc64cab56f48edb863c7c1c43649ec4d78ff71a1acb5366ad723b6021", size = 10700888, upload-time = "2026-02-20T21:51:09.673Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/cb3620dc48c5d335ba7876edfef636b2f4498eff4a262ff90033b9e88408/ty-0.0.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0fe5038a7136a0e638a2fb1ad06e3d3c4045314c6ba165c9c303b9aeb4623d6c", size = 10078965, upload-time = "2026-02-20T21:51:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/60/27/c77a5a84533fa3b685d592de7b4b108eb1f38851c40fac4e79cc56ec7350/ty-0.0.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d123600a52372677613a719bbb780adeb9b68f47fb5f25acb09171de390e0035", size = 10134659, upload-time = "2026-02-20T21:51:18.311Z" }, + { url = "https://files.pythonhosted.org/packages/43/6e/60af6b88c73469e628ba5253a296da6984e0aa746206f3034c31f1a04ed1/ty-0.0.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb4bc11d32a1bf96a829bf6b9696545a30a196ac77bbc07cc8d3dfee35e03723", size = 10297494, upload-time = "2026-02-20T21:51:39.631Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/612dc0b68224c723faed6adac2bd3f930a750685db76dfe17e6b9e534a83/ty-0.0.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dda2efbf374ba4cd704053d04e32f2f784e85c2ddc2400006b0f96f5f7e4b667", size = 10791944, upload-time = "2026-02-20T21:51:37.13Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/f4ada0fd08a9e4138fe3fd2bcd3797753593f423f19b1634a814b9b2a401/ty-0.0.18-py3-none-win32.whl", hash = "sha256:c5768607c94977dacddc2f459ace6a11a408a0f57888dd59abb62d28d4fee4f7", size = 9677964, upload-time = "2026-02-20T21:51:42.039Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/090ed9746e5c59fc26d8f5f96dc8441825171f1f47752f1778dad690b08b/ty-0.0.18-py3-none-win_amd64.whl", hash = "sha256:b78d0fa1103d36fc2fce92f2092adace52a74654ab7884d54cdaec8eb5016a4d", size = 10636576, upload-time = "2026-02-20T21:51:29.159Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/5dd60904c8105cda4d0be34d3a446c180933c76b84ae0742e58f02133713/ty-0.0.18-py3-none-win_arm64.whl", hash = "sha256:01770c3c82137c6b216aa3251478f0b197e181054ee92243772de553d3586398", size = 10095449, upload-time = "2026-02-20T21:51:34.914Z" }, ] [[package]] @@ -2581,44 +2581,44 @@ wheels = [ [[package]] name = "uuid-utils" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/7c/3a926e847516e67bc6838634f2e54e24381105b4e80f9338dc35cca0086b/uuid_utils-0.14.0.tar.gz", hash = "sha256:fc5bac21e9933ea6c590433c11aa54aaca599f690c08069e364eb13a12f670b4", size = 22072, upload-time = "2026-01-20T20:37:15.729Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/42/42d003f4a99ddc901eef2fd41acb3694163835e037fb6dde79ad68a72342/uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f6695c0bed8b18a904321e115afe73b34444bc8451d0ce3244a1ec3b84deb0e5", size = 601786, upload-time = "2026-01-20T20:37:09.843Z" }, - { url = "https://files.pythonhosted.org/packages/96/e6/775dfb91f74b18f7207e3201eb31ee666d286579990dc69dd50db2d92813/uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4f0a730bbf2d8bb2c11b93e1005e91769f2f533fa1125ed1f00fd15b6fcc732b", size = 303943, upload-time = "2026-01-20T20:37:18.767Z" }, - { url = "https://files.pythonhosted.org/packages/17/82/ea5f5e85560b08a1f30cdc65f75e76494dc7aba9773f679e7eaa27370229/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ce3fd1a4fdedae618fc3edc8faf91897012469169d600133470f49fd699ed3", size = 340467, upload-time = "2026-01-20T20:37:11.794Z" }, - { url = "https://files.pythonhosted.org/packages/ca/33/54b06415767f4569882e99b6470c6c8eeb97422686a6d432464f9967fd91/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ae4a98416a440e78f7d9543d11b11cae4bab538b7ed94ec5da5221481748f2", size = 346333, upload-time = "2026-01-20T20:37:12.818Z" }, - { url = "https://files.pythonhosted.org/packages/cb/10/a6bce636b8f95e65dc84bf4a58ce8205b8e0a2a300a38cdbc83a3f763d27/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:971e8c26b90d8ae727e7f2ac3ee23e265971d448b3672882f2eb44828b2b8c3e", size = 470859, upload-time = "2026-01-20T20:37:01.512Z" }, - { url = "https://files.pythonhosted.org/packages/8a/27/84121c51ea72f013f0e03d0886bcdfa96b31c9b83c98300a7bd5cc4fa191/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5cde1fa82804a8f9d2907b7aec2009d440062c63f04abbdb825fce717a5e860", size = 341988, upload-time = "2026-01-20T20:37:22.881Z" }, - { url = "https://files.pythonhosted.org/packages/90/a4/01c1c7af5e6a44f20b40183e8dac37d6ed83e7dc9e8df85370a15959b804/uuid_utils-0.14.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7343862a2359e0bd48a7f3dfb5105877a1728677818bb694d9f40703264a2db", size = 365784, upload-time = "2026-01-20T20:37:10.808Z" }, - { url = "https://files.pythonhosted.org/packages/04/f0/65ee43ec617b8b6b1bf2a5aecd56a069a08cca3d9340c1de86024331bde3/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c51e4818fdb08ccec12dc7083a01f49507b4608770a0ab22368001685d59381b", size = 523750, upload-time = "2026-01-20T20:37:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/95/d3/6bf503e3f135a5dfe705a65e6f89f19bccd55ac3fb16cb5d3ec5ba5388b8/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:181bbcccb6f93d80a8504b5bd47b311a1c31395139596edbc47b154b0685b533", size = 615818, upload-time = "2026-01-20T20:37:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/df/6c/99937dd78d07f73bba831c8dc9469dfe4696539eba2fc269ae1b92752f9e/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:5c8ae96101c3524ba8dbf762b6f05e9e9d896544786c503a727c5bf5cb9af1a7", size = 580831, upload-time = "2026-01-20T20:37:19.691Z" }, - { url = "https://files.pythonhosted.org/packages/44/fa/bbc9e2c25abd09a293b9b097a0d8fc16acd6a92854f0ec080f1ea7ad8bb3/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00ac3c6edfdaff7e1eed041f4800ae09a3361287be780d7610a90fdcde9befdc", size = 546333, upload-time = "2026-01-20T20:37:03.117Z" }, - { url = "https://files.pythonhosted.org/packages/e7/9b/e5e99b324b1b5f0c62882230455786df0bc66f67eff3b452447e703f45d2/uuid_utils-0.14.0-cp39-abi3-win32.whl", hash = "sha256:ec2fd80adf8e0e6589d40699e6f6df94c93edcc16dd999be0438dd007c77b151", size = 177319, upload-time = "2026-01-20T20:37:04.208Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/2c7d417ea483b6ff7820c948678fdf2ac98899dc7e43bb15852faa95acaf/uuid_utils-0.14.0-cp39-abi3-win_amd64.whl", hash = "sha256:efe881eb43a5504fad922644cb93d725fd8a6a6d949bd5a4b4b7d1a1587c7fd1", size = 182566, upload-time = "2026-01-20T20:37:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/b8/86/49e4bdda28e962fbd7266684171ee29b3d92019116971d58783e51770745/uuid_utils-0.14.0-cp39-abi3-win_arm64.whl", hash = "sha256:32b372b8fd4ebd44d3a219e093fe981af4afdeda2994ee7db208ab065cfcd080", size = 182809, upload-time = "2026-01-20T20:37:05.139Z" }, - { url = "https://files.pythonhosted.org/packages/f1/03/1f1146e32e94d1f260dfabc81e1649102083303fb4ad549775c943425d9a/uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:762e8d67992ac4d2454e24a141a1c82142b5bde10409818c62adbe9924ebc86d", size = 587430, upload-time = "2026-01-20T20:37:24.998Z" }, - { url = "https://files.pythonhosted.org/packages/87/ba/d5a7469362594d885fd9219fe9e851efbe65101d3ef1ef25ea321d7ce841/uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:40be5bf0b13aa849d9062abc86c198be6a25ff35316ce0b89fc25f3bac6d525e", size = 298106, upload-time = "2026-01-20T20:37:23.896Z" }, - { url = "https://files.pythonhosted.org/packages/8a/11/3dafb2a5502586f59fd49e93f5802cd5face82921b3a0f3abb5f357cb879/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:191a90a6f3940d1b7322b6e6cceff4dd533c943659e0a15f788674407856a515", size = 333423, upload-time = "2026-01-20T20:37:17.828Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f2/c8987663f0cdcf4d717a36d85b5db2a5589df0a4e129aa10f16f4380ef48/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa4525f4ad82f9d9c842f9a3703f1539c1808affbaec07bb1b842f6b8b96aa5", size = 338659, upload-time = "2026-01-20T20:37:14.286Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c8/929d81665d83f0b2ffaecb8e66c3091a50f62c7cb5b65e678bd75a96684e/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdbd82ff20147461caefc375551595ecf77ebb384e46267f128aca45a0f2cdfc", size = 467029, upload-time = "2026-01-20T20:37:08.277Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a0/27d7daa1bfed7163f4ccaf52d7d2f4ad7bb1002a85b45077938b91ee584f/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff57e8a5d540006ce73cf0841a643d445afe78ba12e75ac53a95ca2924a56be", size = 333298, upload-time = "2026-01-20T20:37:07.271Z" }, - { url = "https://files.pythonhosted.org/packages/63/d4/acad86ce012b42ce18a12f31ee2aa3cbeeb98664f865f05f68c882945913/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fd9112ca96978361201e669729784f26c71fecc9c13a7f8a07162c31bd4d1e2", size = 359217, upload-time = "2026-01-20T20:36:59.687Z" }, +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, + { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, + { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, + { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, + { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/6c64bdbf71f58ccde7919e00491812556f446a5291573af92c49a5e9aaef/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b197cd5424cf89fb019ca7f53641d05bfe34b1879614bed111c9c313b5574cd8", size = 591617, upload-time = "2026-02-20T22:50:24.532Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f0/758c3b0fb0c4871c7704fef26a5bc861de4f8a68e4831669883bebe07b0f/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:12c65020ba6cb6abe1d57fcbfc2d0ea0506c67049ee031714057f5caf0f9bc9c", size = 303702, upload-time = "2026-02-20T22:50:40.687Z" }, + { url = "https://files.pythonhosted.org/packages/85/89/d91862b544c695cd58855efe3201f83894ed82fffe34500774238ab8eba7/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b5d2ad28063d422ccc2c28d46471d47b61a58de885d35113a8f18cb547e25bf", size = 337678, upload-time = "2026-02-20T22:50:39.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6b/cf342ba8a898f1de024be0243fac67c025cad530c79ea7f89c4ce718891a/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da2234387b45fde40b0fedfee64a0ba591caeea9c48c7698ab6e2d85c7991533", size = 343711, upload-time = "2026-02-20T22:50:43.965Z" }, + { url = "https://files.pythonhosted.org/packages/b3/20/049418d094d396dfa6606b30af925cc68a6670c3b9103b23e6990f84b589/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50fffc2827348c1e48972eed3d1c698959e63f9d030aa5dd82ba451113158a62", size = 476731, upload-time = "2026-02-20T22:50:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/77/a1/0857f64d53a90321e6a46a3d4cc394f50e1366132dcd2ae147f9326ca98b/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dbe718765f70f5b7f9b7f66b6a937802941b1cc56bcf642ce0274169741e01", size = 338902, upload-time = "2026-02-20T22:50:33.927Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d0/5bf7cbf1ac138c92b9ac21066d18faf4d7e7f651047b700eb192ca4b9fdb/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:258186964039a8e36db10810c1ece879d229b01331e09e9030bc5dcabe231bd2", size = 364700, upload-time = "2026-02-20T22:50:21.732Z" }, ] [[package]] name = "uvicorn" -version = "0.40.0" +version = "0.41.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, ] [[package]] From 4ad4e7b87f21c34c1fa8eb275a8addbf8469fc8e Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Wed, 25 Feb 2026 20:02:14 +0530 Subject: [PATCH 15/22] Address coderabbit review comments - Bind mount uv to fix unconditional copying uv/uvx binaries to slim image - Add variant validation - Guard finalize against unintended pypi-publish skips --- .github/workflows/release-python.yml | 4 ++-- python-interpreter/Dockerfile | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 8acb0d4..b42b7fc 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -198,7 +198,7 @@ jobs: !cancelled() && needs.validate.result == 'success' && (needs.pypi-publish.result == 'success' - || needs.pypi-publish.result == 'skipped') + || (needs.pypi-publish.result == 'skipped' && inputs.skip_pypi)) && (needs.docker.result == 'success' || needs.docker.result == 'skipped') uses: ./.github/workflows/release-finalize.yml @@ -214,7 +214,7 @@ jobs: bump-version: needs: [validate, finalize] - if: false # Disabled: direct push blocked by branch protection + if: false # Disabled: direct push blocked by branch protection runs-on: ubuntu-latest permissions: contents: write diff --git a/python-interpreter/Dockerfile b/python-interpreter/Dockerfile index c2e99e7..8d85b2a 100644 --- a/python-interpreter/Dockerfile +++ b/python-interpreter/Dockerfile @@ -33,16 +33,23 @@ RUN --mount=type=cache,target=/root/.cache/uv \ FROM python:3.13-alpine ARG VARIANT=full +# Validate VARIANT early so typos fail loudly instead of silently producing a slim build. +RUN if [ "$VARIANT" != "full" ] && [ "$VARIANT" != "slim" ]; then \ + echo "ERROR: VARIANT must be 'full' or 'slim', got: '$VARIANT'" >&2; \ + exit 1; \ + fi + RUN if [ "$VARIANT" = "full" ]; then \ apk add --no-cache nodejs npm git; \ fi # Install uv and uvx for Python-based MCP server support (full variant only) -COPY --from=ghcr.io/astral-sh/uv:0.10.0 /uv /uvx /uv-bins/ -RUN if [ "$VARIANT" = "full" ]; then \ - mv /uv-bins/uv /uv-bins/uvx /bin/; \ - fi && \ - rm -rf /uv-bins +# Use bind mounts so the uv binaries are never written into any image layer for slim builds. +RUN --mount=type=bind,from=ghcr.io/astral-sh/uv:0.10.0,source=/uv,target=/tmp/uv \ + --mount=type=bind,from=ghcr.io/astral-sh/uv:0.10.0,source=/uvx,target=/tmp/uvx \ + if [ "$VARIANT" = "full" ]; then \ + cp /tmp/uv /tmp/uvx /bin/; \ + fi # Set working directory WORKDIR /app From d5d5cb3ac553b78de979f1836ea1ddb60bf78ee9 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 26 Feb 2026 12:45:53 +0530 Subject: [PATCH 16/22] Align default behaviour between interpreters - Make the default port of the python-interpreter 8085 - Serve the chat UI under /chat/ui regardless of path setting --- python-interpreter/Dockerfile | 2 +- python-interpreter/README.md | 4 ++-- python-interpreter/packages/afm-cli/README.md | 4 ++-- python-interpreter/packages/afm-core/src/afm/cli.py | 6 +++--- .../packages/afm-core/src/afm/interfaces/web_chat.py | 2 +- .../packages/afm-core/src/afm/interfaces/webhook.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/python-interpreter/Dockerfile b/python-interpreter/Dockerfile index 8d85b2a..8a09f91 100644 --- a/python-interpreter/Dockerfile +++ b/python-interpreter/Dockerfile @@ -66,7 +66,7 @@ ENV PYTHONUNBUFFERED=1 ENV AFM_RUNTIME=docker # Expose default port for web interfaces -EXPOSE 8000 +EXPOSE 8085 # Entry point to run the afm CLI ENTRYPOINT ["afm"] diff --git a/python-interpreter/README.md b/python-interpreter/README.md index 2b90387..ea2e5fc 100644 --- a/python-interpreter/README.md +++ b/python-interpreter/README.md @@ -41,7 +41,7 @@ uv run afm path/to/agent.afm.md Configuration via environment variables or CLI options: - `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc. (Required based on provider) -- HTTP port can be set via `-p` or `--port` (default: 8000) +- HTTP port can be set via `-p` or `--port` (default: 8085) ## Running with Docker @@ -51,7 +51,7 @@ The Docker image bundles the LangChain execution backend. # Using the pre-built image docker run -v $(pwd)/path/to/agent.afm.md:/app/agent.afm.md \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ - -p 8000:8000 \ + -p 8085:8085 \ ghcr.io/wso2/afm-langchain-interpreter:latest run /app/agent.afm.md # Or build locally diff --git a/python-interpreter/packages/afm-cli/README.md b/python-interpreter/packages/afm-cli/README.md index e1d5df3..a4a9e71 100644 --- a/python-interpreter/packages/afm-cli/README.md +++ b/python-interpreter/packages/afm-cli/README.md @@ -96,7 +96,7 @@ afm framework list ### CLI Options -- `-p, --port PORT` - Port for web interfaces (default: 8000) +- `-p, --port PORT` - Port for web interfaces (default: 8085) - `--help` - Show help message ## Features @@ -126,7 +126,7 @@ docker build -t afm-langchain-interpreter . # Run with an AFM file mounted docker run -v $(pwd)/agent.afm.md:/app/agent.afm.md \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ - -p 8000:8000 \ + -p 8085:8085 \ afm-langchain-interpreter afm /app/agent.afm.md ``` diff --git a/python-interpreter/packages/afm-core/src/afm/cli.py b/python-interpreter/packages/afm-core/src/afm/cli.py index 34be055..38e394a 100644 --- a/python-interpreter/packages/afm-core/src/afm/cli.py +++ b/python-interpreter/packages/afm-core/src/afm/cli.py @@ -59,7 +59,7 @@ def create_unified_app( webhook_interface: WebhookInterface | None = None, startup_event: asyncio.Event | None = None, host: str = "0.0.0.0", - port: int = 8000, + port: int = 8085, ) -> FastAPI: if webchat_interface is None and webhook_interface is None: raise ValueError("At least one HTTP interface must be provided") @@ -348,9 +348,9 @@ def validate(file: Path) -> None: @click.option( "--port", "-p", - default=8000, + default=8085, type=int, - help="HTTP port for web interfaces (default: 8000)", + help="HTTP port for web interfaces (default: 8085)", ) @click.option( "--host", diff --git a/python-interpreter/packages/afm-core/src/afm/interfaces/web_chat.py b/python-interpreter/packages/afm-core/src/afm/interfaces/web_chat.py index 9e36964..e8583ed 100644 --- a/python-interpreter/packages/afm-core/src/afm/interfaces/web_chat.py +++ b/python-interpreter/packages/afm-core/src/afm/interfaces/web_chat.py @@ -82,7 +82,7 @@ def create_webchat_router( path: str = "/chat", ) -> APIRouter: router = APIRouter() - ui_path = f"{path}/ui" + ui_path = "/chat/ui" raw_icon_url = agent.afm.metadata.icon_url icon_url = "" if raw_icon_url is None else html.escape(str(raw_icon_url)) icon_style = "" if icon_url else "display:none;" diff --git a/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py b/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py index 9db4e5d..e578fbb 100644 --- a/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py +++ b/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py @@ -375,7 +375,7 @@ def create_webhook_app( else: effective_host = "localhost" - effective_port = port if port else 8000 + effective_port = port if port else 8085 callback_url = f"http://{effective_host}:{effective_port}{webhook_path}" logger.warning( From 80d241f8a5b174946040cc40cdb5cf64377feb1f Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 26 Feb 2026 12:47:08 +0530 Subject: [PATCH 17/22] Add link to chat ui in logs --- python-interpreter/packages/afm-core/src/afm/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python-interpreter/packages/afm-core/src/afm/cli.py b/python-interpreter/packages/afm-core/src/afm/cli.py index 38e394a..12512ff 100644 --- a/python-interpreter/packages/afm-core/src/afm/cli.py +++ b/python-interpreter/packages/afm-core/src/afm/cli.py @@ -472,6 +472,7 @@ def run( if webchat: webchat_path = get_http_path(webchat) click.echo(f" - webchat at http://{host}:{port}{webchat_path}") + click.echo(f" - webchat UI at http://{host}:{port}/chat/ui") if has_console: click.echo(" - consolechat (interactive)") From 9791b820dcff5677023e4429755ded02d8a6f762 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 26 Feb 2026 14:00:33 +0530 Subject: [PATCH 18/22] Demote MCP stdio JSONRPC parse errors from ERROR to DEBUG Package managers (uvx, npx) emit install output to stdout on first run, which the MCP client logs as ERROR. Add MCPStdioNoiseFilter to silence this noise in normal mode while keeping it visible at DEBUG level. --- .../src/afm_langchain/backend.py | 10 +- .../src/afm_langchain/logging_utils.py | 41 ++++++ .../afm-langchain/tests/test_logging_utils.py | 119 ++++++++++++++++++ 3 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 python-interpreter/packages/afm-langchain/src/afm_langchain/logging_utils.py create mode 100644 python-interpreter/packages/afm-langchain/tests/test_logging_utils.py diff --git a/python-interpreter/packages/afm-langchain/src/afm_langchain/backend.py b/python-interpreter/packages/afm-langchain/src/afm_langchain/backend.py index df3b924..0808e39 100644 --- a/python-interpreter/packages/afm-langchain/src/afm_langchain/backend.py +++ b/python-interpreter/packages/afm-langchain/src/afm_langchain/backend.py @@ -21,10 +21,6 @@ from types import TracebackType from typing import Any -from langchain_core.language_models import BaseChatModel -from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage -from langchain_core.tools import BaseTool - from afm.exceptions import AgentError, InputValidationError, OutputValidationError from afm.models import ( AFMRecord, @@ -36,12 +32,18 @@ coerce_output_to_schema, validate_input, ) +from langchain_core.language_models import BaseChatModel +from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage +from langchain_core.tools import BaseTool +from .logging_utils import MCPStdioNoiseFilter from .providers import create_model_provider from .tools.mcp import MCPManager logger = logging.getLogger(__name__) +logging.getLogger("mcp.client.stdio").addFilter(MCPStdioNoiseFilter()) + class LangChainRunner: def __init__( diff --git a/python-interpreter/packages/afm-langchain/src/afm_langchain/logging_utils.py b/python-interpreter/packages/afm-langchain/src/afm_langchain/logging_utils.py new file mode 100644 index 0000000..0381063 --- /dev/null +++ b/python-interpreter/packages/afm-langchain/src/afm_langchain/logging_utils.py @@ -0,0 +1,41 @@ +# Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +import logging + + +class MCPStdioNoiseFilter(logging.Filter): + """Demotes harmless MCP stdio parse errors from ERROR to DEBUG. + + Package managers (uvx, npx) write installation output to stdout on + first run, which the MCP client's stdout_reader tries to parse as + JSONRPC and logs as ERROR. These are harmless; the session starts + correctly once the real server begins. See also: + https://github.com/modelcontextprotocol/servers/issues/2895 + """ + + _NOISE_PREFIX = "Failed to parse JSONRPC message from server" + + def filter(self, record: logging.LogRecord) -> bool: + if ( + record.levelno == logging.ERROR + and self._NOISE_PREFIX in record.getMessage() + ): + record.levelno = logging.DEBUG + record.levelname = "DEBUG" + return True diff --git a/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py b/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py new file mode 100644 index 0000000..601067e --- /dev/null +++ b/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py @@ -0,0 +1,119 @@ +# Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import logging + +import pytest + +from afm_langchain.logging_utils import MCPStdioNoiseFilter + + +def make_record(msg: str, level: int = logging.ERROR) -> logging.LogRecord: + record = logging.LogRecord( + name="mcp.client.stdio", + level=level, + pathname="", + lineno=0, + msg=msg, + args=(), + exc_info=None, + ) + return record + + +class TestMCPStdioNoiseFilter: + def setup_method(self): + self.filter = MCPStdioNoiseFilter() + + # --- records that should be demoted --- + + def test_demotes_exact_noise_message(self): + record = make_record("Failed to parse JSONRPC message from server") + result = self.filter.filter(record) + assert result is True # record is kept + assert record.levelno == logging.DEBUG + assert record.levelname == "DEBUG" + + def test_demotes_noise_message_with_trailing_context(self): + # The mcp library may append exception info after the prefix + record = make_record( + "Failed to parse JSONRPC message from server\nTraceback ..." + ) + result = self.filter.filter(record) + assert result is True + assert record.levelno == logging.DEBUG + + def test_demotes_noise_message_with_extra_whitespace(self): + # Prefix may appear with surrounding whitespace + record = make_record(" Failed to parse JSONRPC message from server ") + result = self.filter.filter(record) + assert result is True + assert record.levelno == logging.DEBUG + + # --- records that should NOT be demoted --- + + def test_passes_through_other_error_records_unchanged(self): + record = make_record("Connection refused to MCP server", level=logging.ERROR) + result = self.filter.filter(record) + assert result is True + assert record.levelno == logging.ERROR + assert record.levelname == "ERROR" + + def test_passes_through_warning_records_unchanged(self): + record = make_record( + "Failed to parse JSONRPC message from server", level=logging.WARNING + ) + result = self.filter.filter(record) + assert result is True + assert record.levelno == logging.WARNING + + def test_passes_through_info_records_unchanged(self): + record = make_record("MCP server connected", level=logging.INFO) + result = self.filter.filter(record) + assert result is True + assert record.levelno == logging.INFO + + def test_passes_through_debug_records_unchanged(self): + record = make_record("some debug message", level=logging.DEBUG) + result = self.filter.filter(record) + assert result is True + assert record.levelno == logging.DEBUG + + # --- integration: filter attached to a real logger --- + + def test_filter_integrated_with_logger(self, caplog): + """With the filter attached, noise ERRORs should not appear at ERROR level.""" + logger = logging.getLogger("mcp.client.stdio.test_integration") + logger.addFilter(MCPStdioNoiseFilter()) + + with caplog.at_level(logging.ERROR, logger="mcp.client.stdio.test_integration"): + logger.error("Failed to parse JSONRPC message from server") + + # The record was demoted to DEBUG so it should not appear in ERROR-level capture + error_records = [r for r in caplog.records if r.levelno == logging.ERROR] + assert len(error_records) == 0 + + def test_real_errors_still_propagate(self, caplog): + """Genuine ERROR records must still appear at ERROR level.""" + logger = logging.getLogger("mcp.client.stdio.test_real_errors") + logger.addFilter(MCPStdioNoiseFilter()) + + with caplog.at_level(logging.ERROR, logger="mcp.client.stdio.test_real_errors"): + logger.error("Connection refused by MCP server") + + error_records = [r for r in caplog.records if r.levelno == logging.ERROR] + assert len(error_records) == 1 + assert "Connection refused" in error_records[0].message From 4e242bc3b43e6aae62cbd2234fe29084d659d883 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 26 Feb 2026 15:39:02 +0530 Subject: [PATCH 19/22] Fix CI failure --- python-interpreter/packages/afm-core/src/afm/templates.py | 5 +++-- python-interpreter/packages/afm-core/tests/test_update.py | 1 + .../packages/afm-langchain/tests/test_logging_utils.py | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python-interpreter/packages/afm-core/src/afm/templates.py b/python-interpreter/packages/afm-core/src/afm/templates.py index b0670ac..5029a3c 100644 --- a/python-interpreter/packages/afm-core/src/afm/templates.py +++ b/python-interpreter/packages/afm-core/src/afm/templates.py @@ -17,6 +17,7 @@ from __future__ import annotations import json +from collections.abc import Mapping from typing import Any from .exceptions import ( @@ -103,7 +104,7 @@ def _parse_http_variable(http_part: str, full_expr: str) -> TemplateSegment: def evaluate_template( compiled: CompiledTemplate, payload: Any, - headers: dict[str, str | list[str]] | None, + headers: Mapping[str, str | list[str]] | None, ) -> str: parts: list[str] = [] @@ -144,7 +145,7 @@ def _handle_payload_variable( def _handle_header_variable( - headers: dict[str, str | list[str]] | None, + headers: Mapping[str, str | list[str]] | None, parts: list[str], segment: HeaderVariable, ) -> None: diff --git a/python-interpreter/packages/afm-core/tests/test_update.py b/python-interpreter/packages/afm-core/tests/test_update.py index aed01d4..cae1b96 100644 --- a/python-interpreter/packages/afm-core/tests/test_update.py +++ b/python-interpreter/packages/afm-core/tests/test_update.py @@ -590,6 +590,7 @@ def test_default_package_is_afm_langchain(self): with patch("afm.update.sys") as mock_sys: mock_sys.executable = "/usr/bin/python3" cmd = _detect_install_command() + assert cmd is not None assert "afm-langchain" in cmd diff --git a/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py b/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py index 601067e..e2210c4 100644 --- a/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py +++ b/python-interpreter/packages/afm-langchain/tests/test_logging_utils.py @@ -16,7 +16,6 @@ import logging -import pytest from afm_langchain.logging_utils import MCPStdioNoiseFilter From 4dc533a40e63a4ec8a0b5a842e208e43d5227f93 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Thu, 26 Feb 2026 16:00:35 +0530 Subject: [PATCH 20/22] Define default port in constants file --- .../packages/afm-core/src/afm/cli.py | 7 ++++--- .../packages/afm-core/src/afm/constants.py | 21 +++++++++++++++++++ .../afm-core/src/afm/interfaces/webhook.py | 3 ++- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 python-interpreter/packages/afm-core/src/afm/constants.py diff --git a/python-interpreter/packages/afm-core/src/afm/cli.py b/python-interpreter/packages/afm-core/src/afm/cli.py index 12512ff..ff8b1e4 100644 --- a/python-interpreter/packages/afm-core/src/afm/cli.py +++ b/python-interpreter/packages/afm-core/src/afm/cli.py @@ -27,6 +27,7 @@ import uvicorn from fastapi import FastAPI +from .constants import DEFAULT_HTTP_PORT from .exceptions import AFMError from .interfaces.base import get_http_path, get_interfaces from .interfaces.console_chat import async_run_console_chat @@ -59,7 +60,7 @@ def create_unified_app( webhook_interface: WebhookInterface | None = None, startup_event: asyncio.Event | None = None, host: str = "0.0.0.0", - port: int = 8085, + port: int = DEFAULT_HTTP_PORT, ) -> FastAPI: if webchat_interface is None and webhook_interface is None: raise ValueError("At least one HTTP interface must be provided") @@ -348,9 +349,9 @@ def validate(file: Path) -> None: @click.option( "--port", "-p", - default=8085, + default=DEFAULT_HTTP_PORT, type=int, - help="HTTP port for web interfaces (default: 8085)", + help=f"HTTP port for web interfaces (default: {DEFAULT_HTTP_PORT})", ) @click.option( "--host", diff --git a/python-interpreter/packages/afm-core/src/afm/constants.py b/python-interpreter/packages/afm-core/src/afm/constants.py new file mode 100644 index 0000000..87ce6af --- /dev/null +++ b/python-interpreter/packages/afm-core/src/afm/constants.py @@ -0,0 +1,21 @@ +# Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from typing import Final + +DEFAULT_HTTP_PORT: Final = 8085 diff --git a/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py b/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py index e578fbb..eb6deb5 100644 --- a/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py +++ b/python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py @@ -29,6 +29,7 @@ from fastapi.responses import JSONResponse, PlainTextResponse from pydantic import BaseModel, Field +from ..constants import DEFAULT_HTTP_PORT from ..exceptions import TemplateEvaluationError from ..templates import compile_template, evaluate_template from .base import InterfaceNotFoundError, get_http_path, get_webhook_interface @@ -375,7 +376,7 @@ def create_webhook_app( else: effective_host = "localhost" - effective_port = port if port else 8085 + effective_port = port if port else DEFAULT_HTTP_PORT callback_url = f"http://{effective_host}:{effective_port}{webhook_path}" logger.warning( From 49c15ed8d7b3d544d0fe369db9179ca46f20c24f Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Fri, 27 Feb 2026 11:39:02 +0530 Subject: [PATCH 21/22] Address review comments --- .github/workflows/release-docker.yml | 16 ++++++++-------- .../packages/afm-core/src/afm/variables.py | 11 ++++------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index 497e2dd..77bb188 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -61,15 +61,15 @@ jobs: run: | # GHCR requires lowercase repository names OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') - FULL_IMAGE="ghcr.io/$OWNER_LOWER/$IMAGE_NAME" - TAGS_FULL="$FULL_IMAGE:v$VERSION" - [ "$UPDATE_LATEST" = "true" ] && TAGS_FULL="$TAGS_FULL,$FULL_IMAGE:latest" + BASE_IMAGE="ghcr.io/$OWNER_LOWER/$IMAGE_NAME" + TAGS_FULL="$BASE_IMAGE:v$VERSION" + [ "$UPDATE_LATEST" = "true" ] && TAGS_FULL="$TAGS_FULL,$BASE_IMAGE:latest" echo "TAGS_FULL=$TAGS_FULL" >> $GITHUB_OUTPUT - TAGS_SLIM="$FULL_IMAGE:v$VERSION-slim" - [ "$UPDATE_LATEST" = "true" ] && TAGS_SLIM="$TAGS_SLIM,$FULL_IMAGE:slim" + TAGS_SLIM="$BASE_IMAGE:v$VERSION-slim" + [ "$UPDATE_LATEST" = "true" ] && TAGS_SLIM="$TAGS_SLIM,$BASE_IMAGE:slim" echo "TAGS_SLIM=$TAGS_SLIM" >> $GITHUB_OUTPUT - echo "FULL_IMAGE=$FULL_IMAGE" >> $GITHUB_OUTPUT + echo "BASE_IMAGE=$BASE_IMAGE" >> $GITHUB_OUTPUT - name: Build and push full image uses: docker/build-push-action@v5 @@ -111,7 +111,7 @@ jobs: - name: Scan full Docker image for vulnerabilities uses: aquasecurity/trivy-action@0.34.0 with: - image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }} + image-ref: ${{ steps.docker-tags.outputs.BASE_IMAGE }}:v${{ inputs.version }} format: "sarif" output: "trivy-results-full.sarif" severity: "CRITICAL,HIGH" @@ -129,7 +129,7 @@ jobs: if: ${{ always() && inputs.build_slim }} uses: aquasecurity/trivy-action@0.34.0 with: - image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }}-slim + image-ref: ${{ steps.docker-tags.outputs.BASE_IMAGE }}:v${{ inputs.version }}-slim format: "sarif" output: "trivy-results-slim.sarif" severity: "CRITICAL,HIGH" diff --git a/python-interpreter/packages/afm-core/src/afm/variables.py b/python-interpreter/packages/afm-core/src/afm/variables.py index 4edc4fa..a3f2fdf 100644 --- a/python-interpreter/packages/afm-core/src/afm/variables.py +++ b/python-interpreter/packages/afm-core/src/afm/variables.py @@ -150,7 +150,7 @@ def validate_http_variables(afm_record: AFMRecord) -> None: errored_fields.append("model.provider") if model.url and contains_http_variable(model.url): errored_fields.append("model.url") - if _authentication_contains_http_variable(model.authentication): + if _auth_contains_http_variable(model.authentication): errored_fields.append("model.authentication") # Check interfaces @@ -179,13 +179,10 @@ def validate_http_variables(afm_record: AFMRecord) -> None: for server in metadata.tools.mcp: if contains_http_variable(server.name): errored_fields.append("tools.mcp.name") - # HTTP transport fields: url and authentication if isinstance(server.transport, HttpTransport): if contains_http_variable(server.transport.url): errored_fields.append("tools.mcp.transport.url") - if _authentication_contains_http_variable( - server.transport.authentication - ): + if _auth_contains_http_variable(server.transport.authentication): errored_fields.append("tools.mcp.transport.authentication") if _tool_filter_contains_http_variable(server.tool_filter): errored_fields.append("tools.mcp.tool_filter") @@ -198,7 +195,7 @@ def validate_http_variables(afm_record: AFMRecord) -> None: ) -def _authentication_contains_http_variable( +def _auth_contains_http_variable( auth: ClientAuthentication | None, ) -> bool: if auth is None: @@ -255,7 +252,7 @@ def _subscription_contains_http_variable(subscription: Subscription) -> bool: return True if subscription.secret and contains_http_variable(subscription.secret): return True - if _authentication_contains_http_variable(subscription.authentication): + if _auth_contains_http_variable(subscription.authentication): return True return False From 7f3763bbef2e3235ecea94d17e5bf5e88944c5b9 Mon Sep 17 00:00:00 2001 From: Radith Samarakoon Date: Fri, 27 Feb 2026 12:43:58 +0530 Subject: [PATCH 22/22] Report error keys for StdioTransport http variable errors Use array indices (args[i]) and map keys (env.KEY) instead of generic field paths when reporting http: variable violations in StdioTransport. Added matching validation to the Python implementation and tests for both interpreters. --- ballerina-interpreter/parser.bal | 12 ++- ballerina-interpreter/tests/main_test.bal | 69 ++++++++++++++++ .../packages/afm-core/src/afm/variables.py | 12 +++ .../packages/afm-core/tests/test_variables.py | 78 ++++++++++++++++++- 4 files changed, 162 insertions(+), 9 deletions(-) diff --git a/ballerina-interpreter/parser.bal b/ballerina-interpreter/parser.bal index 7a2a3ef..3420f39 100644 --- a/ballerina-interpreter/parser.bal +++ b/ballerina-interpreter/parser.bal @@ -261,20 +261,18 @@ function validateHttpVariables(AFMRecord afmRecord) returns error? { string[]? args = transport.args; if args is string[] { - foreach string arg in args { - if containsHttpVariable(arg) { - erroredKeys.push("tools.mcp.transport.args"); - break; + foreach int idx in 0 ..< args.length() { + if containsHttpVariable(args[idx]) { + erroredKeys.push(string `tools.mcp.transport.args[${idx}]`); } } } map? env = transport.env; if env is map { - foreach string val in env { + foreach [string, string] [k, val] in env.entries() { if containsHttpVariable(val) { - erroredKeys.push("tools.mcp.transport.env"); - break; + erroredKeys.push("tools.mcp.transport.env." + k); } } } diff --git a/ballerina-interpreter/tests/main_test.bal b/ballerina-interpreter/tests/main_test.bal index 1c3c217..3df4ac9 100644 --- a/ballerina-interpreter/tests/main_test.bal +++ b/ballerina-interpreter/tests/main_test.bal @@ -181,6 +181,75 @@ function testContainsHttpVariable() { test:assertFalse(containsHttpVariable("no variables here")); } +@test:Config +function testValidateHttpVariablesInStdioTransportArgs() { + AFMRecord afmRecord = { + metadata: { + spec_version: "0.3.0", + tools: { + mcp: [ + { + name: "test-server", + transport: { + 'type: stdio, + command: "some-command", + args: ["${http:payload.field}", "--safe-arg", "${http:header.auth}"] + } + } + ] + } + }, + role: "", + instructions: "" + }; + + error? result = validateHttpVariables(afmRecord); + if result is () { + test:assertFail("Expected error for http: variables in stdio transport args"); + } + test:assertTrue(result.message().includes("tools.mcp.transport.args[0]"), + "Expected error to include 'tools.mcp.transport.args[0]'"); + test:assertTrue(result.message().includes("tools.mcp.transport.args[2]"), + "Expected error to include 'tools.mcp.transport.args[2]'"); + test:assertFalse(result.message().includes("tools.mcp.transport.args[1]"), + "Expected error NOT to include 'tools.mcp.transport.args[1]' (clean arg)"); +} + +@test:Config +function testValidateHttpVariablesInStdioTransportEnv() { + AFMRecord afmRecord = { + metadata: { + spec_version: "0.3.0", + tools: { + mcp: [ + { + name: "test-server", + transport: { + 'type: stdio, + command: "some-command", + env: { + "CLEAN_VAR": "safe-value", + "SECRET_KEY": "${http:header.Authorization}" + } + } + } + ] + } + }, + role: "", + instructions: "" + }; + + error? result = validateHttpVariables(afmRecord); + if result is () { + test:assertFail("Expected error for http: variables in stdio transport env"); + } + test:assertTrue(result.message().includes("tools.mcp.transport.env.SECRET_KEY"), + "Expected error to include 'tools.mcp.transport.env.SECRET_KEY'"); + test:assertFalse(result.message().includes("tools.mcp.transport.env.CLEAN_VAR"), + "Expected error NOT to include 'tools.mcp.transport.env.CLEAN_VAR' (clean env var)"); +} + @test:Config function testParseAfmWithoutFrontmatter() { string content = string `# Role diff --git a/python-interpreter/packages/afm-core/src/afm/variables.py b/python-interpreter/packages/afm-core/src/afm/variables.py index a3f2fdf..2247512 100644 --- a/python-interpreter/packages/afm-core/src/afm/variables.py +++ b/python-interpreter/packages/afm-core/src/afm/variables.py @@ -24,6 +24,7 @@ from .models import ( ConsoleChatInterface, HttpTransport, + StdioTransport, WebChatInterface, WebhookInterface, ) @@ -184,6 +185,17 @@ def validate_http_variables(afm_record: AFMRecord) -> None: errored_fields.append("tools.mcp.transport.url") if _auth_contains_http_variable(server.transport.authentication): errored_fields.append("tools.mcp.transport.authentication") + elif isinstance(server.transport, StdioTransport): + if contains_http_variable(server.transport.command): + errored_fields.append("tools.mcp.transport.command") + if server.transport.args: + for i, arg in enumerate(server.transport.args): + if contains_http_variable(arg): + errored_fields.append(f"tools.mcp.transport.args[{i}]") + if server.transport.env: + for key, value in server.transport.env.items(): + if contains_http_variable(value): + errored_fields.append(f"tools.mcp.transport.env.{key}") if _tool_filter_contains_http_variable(server.tool_filter): errored_fields.append("tools.mcp.tool_filter") diff --git a/python-interpreter/packages/afm-core/tests/test_variables.py b/python-interpreter/packages/afm-core/tests/test_variables.py index 5819052..7cb2e5b 100644 --- a/python-interpreter/packages/afm-core/tests/test_variables.py +++ b/python-interpreter/packages/afm-core/tests/test_variables.py @@ -16,8 +16,9 @@ import pytest -from afm.exceptions import VariableResolutionError -from afm.variables import resolve_variables +from afm.exceptions import AFMValidationError, VariableResolutionError +from afm.models import AFMRecord, AgentMetadata, MCPServer, StdioTransport, Tools +from afm.variables import resolve_variables, validate_http_variables class TestResolveVariables: @@ -55,3 +56,76 @@ def test_skips_commented_variables(self, monkeypatch) -> None: def test_skips_http_variables(self) -> None: content = "Payload: ${http:payload.name}" assert resolve_variables(content) == content + + +def _make_afm_record_with_stdio_transport( + args: list[str] | None = None, + env: dict[str, str] | None = None, + command: str = "npx", +) -> AFMRecord: + """Helper to create a minimal AFMRecord with a StdioTransport MCP server.""" + return AFMRecord( + metadata=AgentMetadata( + tools=Tools( + mcp=[ + MCPServer( + name="test-server", + transport=StdioTransport( + command=command, + args=args, + env=env, + ), + ) + ] + ) + ), + role="Test role", + instructions="Test instructions", + ) + + +class TestValidateHttpVariablesStdioTransport: + def test_args_reports_individual_indices(self) -> None: + record = _make_afm_record_with_stdio_transport( + args=[ + "${http:payload.url}", + "clean-arg", + "${http:header.auth}", + ] + ) + with pytest.raises(AFMValidationError) as exc_info: + validate_http_variables(record) + msg = str(exc_info.value) + assert "tools.mcp.transport.args[0]" in msg + assert "tools.mcp.transport.args[1]" not in msg + assert "tools.mcp.transport.args[2]" in msg + + def test_env_reports_individual_keys(self) -> None: + record = _make_afm_record_with_stdio_transport( + env={ + "SECRET_KEY": "${http:payload.secret}", + "CLEAN_VAR": "plain-value", + "API_URL": "${http:payload.api}", + } + ) + with pytest.raises(AFMValidationError) as exc_info: + validate_http_variables(record) + msg = str(exc_info.value) + assert "tools.mcp.transport.env.SECRET_KEY" in msg + assert "tools.mcp.transport.env.CLEAN_VAR" not in msg + assert "tools.mcp.transport.env.API_URL" in msg + + def test_command_reports_violation(self) -> None: + record = _make_afm_record_with_stdio_transport(command="${http:payload.cmd}") + with pytest.raises(AFMValidationError) as exc_info: + validate_http_variables(record) + assert "tools.mcp.transport.command" in str(exc_info.value) + + def test_no_error_when_no_http_variables(self) -> None: + record = _make_afm_record_with_stdio_transport( + command="npx", + args=["--port", "3000"], + env={"HOME": "/home/user"}, + ) + # Should not raise + validate_http_variables(record)