From 3a05cac93e463960faea96ea35c47cb8c31eac58 Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Sat, 26 Jul 2025 13:49:20 +0200 Subject: [PATCH 001/253] Make mypy run through on solidlsp --- pyproject.toml | 2 +- .../clangd_language_server.py | 19 +-- src/solidlsp/language_servers/clojure_lsp.py | 26 ++-- src/solidlsp/language_servers/common.py | 3 + .../csharp_language_server.py | 34 +++--- .../language_servers/dart_language_server.py | 23 ++-- .../language_servers/eclipse_jdtls.py | 29 ++--- .../elixir_tools/elixir_tools.py | 21 ++-- src/solidlsp/language_servers/gopls.py | 17 +-- src/solidlsp/language_servers/intelephense.py | 18 +-- src/solidlsp/language_servers/jedi_server.py | 15 +-- .../kotlin_language_server.py | 15 +-- src/solidlsp/language_servers/omnisharp.py | 18 +-- .../language_servers/pyright_server.py | 15 +-- .../language_servers/rust_analyzer.py | 19 +-- src/solidlsp/language_servers/solargraph.py | 25 ++-- src/solidlsp/language_servers/terraform_ls.py | 14 ++- .../typescript_language_server.py | 22 ++-- .../language_servers/vts_language_server.py | 20 +-- src/solidlsp/ls.py | 114 +++++++++--------- src/solidlsp/ls_config.py | 2 +- src/solidlsp/ls_handler.py | 28 ++--- src/solidlsp/ls_request.py | 7 +- src/solidlsp/ls_utils.py | 15 ++- .../lsp_protocol_handler/lsp_requests.py | 6 +- src/solidlsp/lsp_protocol_handler/server.py | 4 +- src/solidlsp/settings.py | 4 +- 27 files changed, 291 insertions(+), 244 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e79567c2..24797bf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,7 +119,7 @@ _black_format = "black src scripts test" _ruff_format = "ruff check --fix src scripts test" lint = ["_black_check", "_ruff_check"] format = ["_ruff_format", "_black_format"] -_mypy = "mypy src/serena" +_mypy = "mypy src/serena src/solidlsp" type-check = ["_mypy"] [tool.ruff] diff --git a/src/solidlsp/language_servers/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server.py index a770d78e..03cb3360 100644 --- a/src/solidlsp/language_servers/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server.py @@ -7,6 +7,7 @@ import os import pathlib import stat import threading +from typing import Any, cast from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig @@ -126,9 +127,9 @@ class ClangdLanguageServer(SolidLanguageServer): ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the Clangd Language Server, waits for the server to be ready and yields the LanguageServer instance. @@ -142,7 +143,7 @@ class ClangdLanguageServer(SolidLanguageServer): # LanguageServer has been shutdown """ - def register_capability_handler(params): + def register_capability_handler(params: Any) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": @@ -150,24 +151,24 @@ class ClangdLanguageServer(SolidLanguageServer): self.resolve_main_method_available.set() return - def lang_status_handler(params): + def lang_status_handler(params: Any) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": self.service_ready_event.set() - def execute_client_command_handler(params): + def execute_client_command_handler(params: Any) -> list: return [] - def do_nothing(params): + def do_nothing(params: Any) -> None: return - def check_experimental_status(params): + def check_experimental_status(params: Any) -> None: if params["quiescent"] == True: self.server_ready.set() - def window_log_message(msg): + def window_log_message(msg: Any) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", register_capability_handler) @@ -188,7 +189,7 @@ class ClangdLanguageServer(SolidLanguageServer): logging.INFO, ) init_response = self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 + assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore assert "completionProvider" in init_response["capabilities"] assert init_response["capabilities"]["completionProvider"] == { "triggerCharacters": [".", "<", ">", ":", '"', "/", "*"], diff --git a/src/solidlsp/language_servers/clojure_lsp.py b/src/solidlsp/language_servers/clojure_lsp.py index f973db15..e1098e6f 100644 --- a/src/solidlsp/language_servers/clojure_lsp.py +++ b/src/solidlsp/language_servers/clojure_lsp.py @@ -9,6 +9,7 @@ import shutil import stat import subprocess import threading +from typing import cast from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig @@ -26,7 +27,7 @@ def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedP ) -def verify_clojure_cli(): +def verify_clojure_cli() -> None: install_msg = "Please install the official Clojure CLI from:\n https://clojure.org/guides/getting_started" if shutil.which("clojure") is None: raise FileNotFoundError("`clojure` not found.\n" + install_msg) @@ -125,7 +126,7 @@ class ClojureLSP(SolidLanguageServer): def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: """Returns the init params for clojure-lsp.""" root_uri = pathlib.Path(repository_absolute_path).as_uri() - return { # type: ignore + result = { # type: ignore "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": root_uri, @@ -153,9 +154,10 @@ class ClojureLSP(SolidLanguageServer): "trace": "off", "workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}], } + return cast(InitializeParams, result) - def _start_server(self): - def register_capability_handler(params): + def _start_server(self) -> None: + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": @@ -163,24 +165,24 @@ class ClojureLSP(SolidLanguageServer): self.resolve_main_method_available.set() return - def lang_status_handler(params): + def lang_status_handler(params: dict) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": self.service_ready_event.set() - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def check_experimental_status(params): - if params["quiescent"] == True: + def check_experimental_status(params: dict) -> None: + if params["quiescent"] is True: self.server_ready.set() - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", register_capability_handler) @@ -202,11 +204,11 @@ class ClojureLSP(SolidLanguageServer): logging.INFO, ) init_response = self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 + assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore assert "completionProvider" in init_response["capabilities"] # Clojure-lsp completion provider capabilities are more flexible than other servers' completion_provider = init_response["capabilities"]["completionProvider"] - assert completion_provider["resolveProvider"] == True + assert completion_provider["resolveProvider"] is True assert "triggerCharacters" in completion_provider self.server.notify.initialized({}) # after initialize, Clojure-lsp is ready to serve diff --git a/src/solidlsp/language_servers/common.py b/src/solidlsp/language_servers/common.py index a0973e56..68be342f 100644 --- a/src/solidlsp/language_servers/common.py +++ b/src/solidlsp/language_servers/common.py @@ -94,6 +94,9 @@ class RuntimeDependencyCollection: @staticmethod def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> None: + if not dep.url: + raise ValueError(f"Dependency {dep.id} has no URL") + if dep.archive_type == "gz" and dep.binary_name: dest = os.path.join(target_dir, dep.binary_name) FileUtils.download_and_extract_archive(logger, dep.url, dest, dep.archive_type) diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index 88bf9bc4..080ea88a 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -13,8 +13,9 @@ import tarfile import threading import urllib.request import zipfile +from collections.abc import Iterable from pathlib import Path -from typing import cast +from typing import Any, cast from overrides import override @@ -23,7 +24,7 @@ from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_exceptions import LanguageServerException from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import PathUtils -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings @@ -126,7 +127,7 @@ RUNTIME_DEPENDENCIES = [ ] -def breadth_first_file_scan(root_dir): +def breadth_first_file_scan(root_dir: str) -> Iterable[str]: """ Perform a breadth-first scan of files in the given directory. Yields file paths in breadth-first order. @@ -148,7 +149,7 @@ def breadth_first_file_scan(root_dir): pass -def find_solution_or_project_file(root_dir) -> str | None: +def find_solution_or_project_file(root_dir: str) -> str | None: """ Find the first .sln file in breadth-first order. If no .sln file is found, look for a .csproj file. @@ -296,6 +297,7 @@ class CSharpLanguageServer(SolidLanguageServer): package_version = lang_server_dep.package_version server_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / f"{package_name}.{package_version}" + assert lang_server_dep.binary_name is not None server_dll = server_dir / lang_server_dep.binary_name if server_dll.exists(): @@ -304,6 +306,8 @@ class CSharpLanguageServer(SolidLanguageServer): # Download and install the language server logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO) + assert package_version is not None + assert package_name is not None package_path = cls._download_nuget_package_direct(logger, package_name, package_version, solidlsp_settings) # Extract and install @@ -423,6 +427,7 @@ class CSharpLanguageServer(SolidLanguageServer): # Download .NET 9 runtime using config dotnet_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "dotnet-runtime-9.0" + assert runtime_dep.binary_name is not None dotnet_exe = dotnet_dir / runtime_dep.binary_name if dotnet_exe.exists(): @@ -434,6 +439,7 @@ class CSharpLanguageServer(SolidLanguageServer): dotnet_dir.mkdir(parents=True, exist_ok=True) url = runtime_dep.url + assert url is not None archive_type = runtime_dep.archive_type # Download the runtime @@ -519,11 +525,11 @@ class CSharpLanguageServer(SolidLanguageServer): }, ) - def _start_server(self): - def do_nothing(params): + def _start_server(self) -> None: + def do_nothing(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: """Log messages from the language server.""" message_text = msg.get("message", "") level = msg.get("type", 4) # Default to Log level @@ -533,7 +539,7 @@ class CSharpLanguageServer(SolidLanguageServer): self.logger.log(f"LSP: {message_text}", level_map.get(level, logging.DEBUG)) - def handle_progress(params): + def handle_progress(params: dict) -> None: """Handle progress notifications from the language server.""" token = params.get("token", "") value = params.get("value", {}) @@ -567,10 +573,10 @@ class CSharpLanguageServer(SolidLanguageServer): message = value.get("message", "Operation completed") self.logger.log(f"Progress [{token}]: {message}", logging.INFO) - def handle_workspace_configuration(params): + def handle_workspace_configuration(params: dict) -> list: """Handle workspace/configuration requests from the server.""" items = params.get("items", []) - result = [] + result: list[Any] = [] for item in items: section = item.get("section", "") @@ -613,12 +619,12 @@ class CSharpLanguageServer(SolidLanguageServer): return result - def handle_work_done_progress_create(params): + def handle_work_done_progress_create(params: dict) -> None: """Handle work done progress create requests.""" # Just acknowledge the request return - def handle_register_capability(params): + def handle_register_capability(params: dict) -> None: """Handle client/registerCapability requests.""" # Just acknowledge the request - we don't need to track these for now return @@ -683,13 +689,13 @@ class CSharpLanguageServer(SolidLanguageServer): logging.INFO, ) - def _force_pull_diagnostics(self, init_response: dict) -> None: + def _force_pull_diagnostics(self, init_response: dict | InitializeResult) -> None: """ Apply the diagnostic capabilities hack. Forces the server to support pull diagnostics. """ capabilities = init_response.get("capabilities", {}) - diagnostic_provider = capabilities.get("diagnosticProvider", {}) + diagnostic_provider: Any = capabilities.get("diagnosticProvider", {}) # Add the diagnostic capabilities hack if isinstance(diagnostic_provider, dict): diff --git a/src/solidlsp/language_servers/dart_language_server.py b/src/solidlsp/language_servers/dart_language_server.py index c9cfcad1..901fc3f4 100644 --- a/src/solidlsp/language_servers/dart_language_server.py +++ b/src/solidlsp/language_servers/dart_language_server.py @@ -2,12 +2,15 @@ import logging import os import pathlib import stat +from typing import cast from solidlsp.ls import SolidLanguageServer from solidlsp.ls_logger import LanguageServerLogger from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from ..ls_config import LanguageServerConfig +from ..lsp_protocol_handler.lsp_types import InitializeParams from .common import RuntimeDependency, RuntimeDependencyCollection @@ -16,7 +19,9 @@ class DartLanguageServer(SolidLanguageServer): Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart. """ - def __init__(self, config, logger, repository_root_path, solidlsp_settings: SolidLSPSettings): + def __init__( + self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings + ) -> None: """ Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -89,7 +94,7 @@ class DartLanguageServer(SolidLanguageServer): return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2" @staticmethod - def _get_initialize_params(repository_absolute_path: str): + def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the Dart Language Server. """ @@ -115,23 +120,23 @@ class DartLanguageServer(SolidLanguageServer): ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Start the language server and yield when the server is ready. """ - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: pass - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", do_nothing) @@ -150,7 +155,7 @@ class DartLanguageServer(SolidLanguageServer): "Sending initialize request to dart-language-server", logging.DEBUG, ) - init_response = self.server.send_request("initialize", initialize_params) + init_response = self.server.send_request("initialize", initialize_params) # type: ignore self.logger.log( f"Received initialize response from dart-language-server: {init_response}", logging.INFO, diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index bf87aab7..1f43d0c8 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -11,6 +11,7 @@ import stat import threading import uuid from pathlib import PurePath +from typing import cast from overrides import override @@ -625,29 +626,29 @@ class EclipseJDTLS(SolidLanguageServer): ], } - initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri] + initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri] # type: ignore bundles = [self.runtime_dependency_paths.intellicode_jar_path] - initialize_params["initializationOptions"]["bundles"] = bundles - initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [ + initialize_params["initializationOptions"]["bundles"] = bundles # type: ignore + initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [ # type: ignore {"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True} ] - for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: + for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: # type: ignore assert "name" in runtime assert "path" in runtime assert os.path.exists(runtime["path"]), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist" - gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"] + gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"] # type: ignore gradle_settings["home"] = self.runtime_dependency_paths.gradle_path gradle_settings["java"]["home"] = self.runtime_dependency_paths.jre_path - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the Eclipse JDTLS Language Server """ - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "textDocument/completion": @@ -665,22 +666,22 @@ class EclipseJDTLS(SolidLanguageServer): self.intellicode_enable_command_available.set() return - def lang_status_handler(params): + def lang_status_handler(params: dict) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": self.service_ready_event.set() - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: assert params["command"] == "_java.reloadBundles.command" assert params["arguments"] == [] return [] - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - def do_nothing(params): + def do_nothing(params: dict) -> None: return self.server.on_request("client/registerCapability", register_capability_handler) @@ -700,13 +701,13 @@ class EclipseJDTLS(SolidLanguageServer): logging.INFO, ) init_response = self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 + assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore assert "completionProvider" not in init_response["capabilities"] assert "executeCommandProvider" not in init_response["capabilities"] self.server.notify.initialized({}) - self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]}) + self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]}) # type: ignore self.intellicode_enable_command_available.wait() diff --git a/src/solidlsp/language_servers/elixir_tools/elixir_tools.py b/src/solidlsp/language_servers/elixir_tools/elixir_tools.py index 5f6a9794..b596bba2 100644 --- a/src/solidlsp/language_servers/elixir_tools/elixir_tools.py +++ b/src/solidlsp/language_servers/elixir_tools/elixir_tools.py @@ -5,6 +5,7 @@ import stat import subprocess import threading import time +from typing import Any, cast from overrides import override @@ -47,7 +48,7 @@ class ElixirTools(SolidLanguageServer): ) @override - def _send_references_request(self, relative_file_path: str, line: int, column: int): + def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list | None: """Override to filter out Next LS internal files from references.""" from solidlsp.ls_utils import PathUtils @@ -70,7 +71,7 @@ class ElixirTools(SolidLanguageServer): return filtered_response @classmethod - def _get_elixir_version(cls): + def _get_elixir_version(cls) -> str | None: """Get the installed Elixir version or None if not found.""" try: result = subprocess.run(["elixir", "--version"], capture_output=True, text=True, check=False) @@ -145,10 +146,12 @@ class ElixirTools(SolidLanguageServer): dependency = runtime_deps[platform_id] executable_path = os.path.join(next_ls_dir, "nextls") + assert dependency.binary_name is not None binary_path = os.path.join(next_ls_dir, dependency.binary_name) if not os.path.exists(executable_path): logger.log(f"Downloading Next LS binary from {dependency.url}", logging.INFO) + assert dependency.url is not None FileUtils.download_file(logger, dependency.url, binary_path) # Make the binary executable on Unix-like systems @@ -243,15 +246,15 @@ class ElixirTools(SolidLanguageServer): "workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """Start Next LS server process""" - def register_capability_handler(params): + def register_capability_handler(params: Any) -> None: return - def window_log_message(msg): + def window_log_message(msg: Any) -> None: """Handle window/logMessage notifications from Next LS""" message_text = msg.get("message", "") self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO) @@ -262,10 +265,10 @@ class ElixirTools(SolidLanguageServer): self.logger.log("Next LS runtime is ready based on official log message", logging.INFO) self.server_ready.set() - def do_nothing(params): + def do_nothing(params: Any) -> None: return - def check_server_ready(params): + def check_server_ready(params: Any) -> None: """ Handle $/progress notifications from Next LS. Keep as fallback for error detection, but primary readiness detection @@ -280,7 +283,7 @@ class ElixirTools(SolidLanguageServer): self.logger.log("Next LS initialization progress completed", logging.INFO) # Note: We don't set server_ready here - we wait for the log message - def work_done_progress(params): + def work_done_progress(params: Any) -> None: """ Handle $/workDoneProgress notifications from Next LS. Keep for completeness but primary readiness detection is via window/logMessage. diff --git a/src/solidlsp/language_servers/gopls.py b/src/solidlsp/language_servers/gopls.py index c21663b7..d74b0f7d 100644 --- a/src/solidlsp/language_servers/gopls.py +++ b/src/solidlsp/language_servers/gopls.py @@ -3,6 +3,7 @@ import os import pathlib import subprocess import threading +from typing import cast from overrides import override @@ -28,7 +29,7 @@ class Gopls(SolidLanguageServer): return super().is_ignored_dirname(dirname) or dirname in ["vendor", "node_modules", "dist", "build"] @staticmethod - def _get_go_version(): + def _get_go_version() -> str | None: """Get the installed Go version or None if not found.""" try: result = subprocess.run(["go", "version"], capture_output=True, text=True, check=False) @@ -39,7 +40,7 @@ class Gopls(SolidLanguageServer): return None @staticmethod - def _get_gopls_version(): + def _get_gopls_version() -> str | None: """Get the installed gopls version or None if not found.""" try: result = subprocess.run(["gopls", "version"], capture_output=True, text=True, check=False) @@ -50,7 +51,7 @@ class Gopls(SolidLanguageServer): return None @staticmethod - def _setup_runtime_dependency(): + def _setup_runtime_dependency() -> bool: """ Check if required Go runtime dependencies are available. Raises RuntimeError with helpful message if dependencies are missing. @@ -117,18 +118,18 @@ class Gopls(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """Start gopls server process""" - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - def do_nothing(params): + def do_nothing(params: dict) -> None: return self.server.on_request("client/registerCapability", register_capability_handler) diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py index 2d1d48e2..25b7e5d6 100644 --- a/src/solidlsp/language_servers/intelephense.py +++ b/src/solidlsp/language_servers/intelephense.py @@ -7,6 +7,7 @@ import os import pathlib import shutil from time import sleep +from typing import cast from overrides import override @@ -14,10 +15,11 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import PlatformId, PlatformUtils -from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams +from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, InitializeParams, LocationLink from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from ..lsp_protocol_handler import lsp_types from .common import RuntimeDependency, RuntimeDependencyCollection @@ -124,18 +126,18 @@ class Intelephense(SolidLanguageServer): ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """Start Intelephense server process""" - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - def do_nothing(params): + def do_nothing(params: dict) -> None: return self.server.on_request("client/registerCapability", register_capability_handler) @@ -170,7 +172,7 @@ class Intelephense(SolidLanguageServer): @override # For some reason, the LS may need longer to process this, so we just retry - def _send_references_request(self, relative_file_path: str, line: int, column: int): + def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None: # TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is # despite the LS having processed requests already. I don't know what causes this, but sleeping # one second helps. It may be that sleeping only once is enough but that's hard to reliably test. @@ -180,7 +182,7 @@ class Intelephense(SolidLanguageServer): return super()._send_references_request(relative_file_path, line, column) @override - def _send_definition_request(self, definition_params: DefinitionParams): + def _send_definition_request(self, definition_params: DefinitionParams) -> Definition | list[LocationLink] | None: # TODO: same as above, also only a problem if the definition is in another file sleep(1) return super()._send_definition_request(definition_params) diff --git a/src/solidlsp/language_servers/jedi_server.py b/src/solidlsp/language_servers/jedi_server.py index 956e00b9..f08cd09a 100644 --- a/src/solidlsp/language_servers/jedi_server.py +++ b/src/solidlsp/language_servers/jedi_server.py @@ -5,6 +5,7 @@ Provides Python specific instantiation of the LanguageServer class. Contains var import logging import os import pathlib +from typing import cast from overrides import override @@ -150,24 +151,24 @@ class JediServer(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the JEDI Language Server """ - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: if params["quiescent"] == True: self.completions_available.set() - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", do_nothing) @@ -188,7 +189,7 @@ class JediServer(SolidLanguageServer): logging.INFO, ) init_response = self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 + assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore assert "completionProvider" in init_response["capabilities"] assert init_response["capabilities"]["completionProvider"] == { "triggerCharacters": [".", "'", '"'], diff --git a/src/solidlsp/language_servers/kotlin_language_server.py b/src/solidlsp/language_servers/kotlin_language_server.py index 5d0d2083..56ba98f4 100644 --- a/src/solidlsp/language_servers/kotlin_language_server.py +++ b/src/solidlsp/language_servers/kotlin_language_server.py @@ -7,6 +7,7 @@ import logging import os import pathlib import stat +from typing import cast from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig @@ -114,7 +115,7 @@ class KotlinLanguageServer(SolidLanguageServer): } kotlin_dependency = runtime_dependencies["runtimeDependency"] - java_dependency = runtime_dependencies["java"][platform_id.value] + java_dependency = runtime_dependencies["java"][platform_id.value] # type: ignore # Setup paths for dependencies static_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "kotlin_language_server") @@ -149,7 +150,7 @@ class KotlinLanguageServer(SolidLanguageServer): # Download and extract Kotlin Language Server if script doesn't exist if not os.path.exists(kotlin_script): logger.log("Downloading Kotlin Language Server...", logging.INFO) - FileUtils.download_and_extract_archive(logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]) + FileUtils.download_and_extract_archive(logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]) # type: ignore # Make script executable on Unix platforms if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"): @@ -420,20 +421,20 @@ class KotlinLanguageServer(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the Kotlin Language Server """ - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", do_nothing) diff --git a/src/solidlsp/language_servers/omnisharp.py b/src/solidlsp/language_servers/omnisharp.py index a23a58a8..9392f1df 100644 --- a/src/solidlsp/language_servers/omnisharp.py +++ b/src/solidlsp/language_servers/omnisharp.py @@ -22,7 +22,7 @@ from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings -def breadth_first_file_scan(root) -> Iterable[str]: +def breadth_first_file_scan(root: str) -> Iterable[str]: """ This function was obtained from https://stackoverflow.com/questions/49654234/is-there-a-breadth-first-search-option-available-in-os-walk-or-equivalent-py It traverses the directory tree in breadth first order. @@ -48,7 +48,7 @@ def breadth_first_file_scan(root) -> Iterable[str]: dirs = next_dirs -def find_least_depth_sln_file(root_dir) -> str | None: +def find_least_depth_sln_file(root_dir: str) -> str | None: for filename in breadth_first_file_scan(root_dir): if filename.endswith(".sln"): return filename @@ -205,12 +205,12 @@ class OmniSharp(SolidLanguageServer): return omnisharp_executable_path, razor_omnisharp_dll_path - def _start_server(self): + def _start_server(self) -> None: """ Starts the Omnisharp Language Server """ - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "textDocument/definition": @@ -220,7 +220,7 @@ class OmniSharp(SolidLanguageServer): if registration["method"] == "textDocument/completion": self.completions_available.set() - def lang_status_handler(params): + def lang_status_handler(params: dict) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? @@ -228,17 +228,17 @@ class OmniSharp(SolidLanguageServer): # self.service_ready_event.set() pass - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: if params["quiescent"] is True: self.server_ready.set() - def workspace_configuration_handler(params): + def workspace_configuration_handler(params: dict) -> list[dict]: # TODO: We do not know the appropriate way to handle this request. Should ideally contact the OmniSharp dev team return [ { diff --git a/src/solidlsp/language_servers/pyright_server.py b/src/solidlsp/language_servers/pyright_server.py index c760ebc1..10cc7886 100644 --- a/src/solidlsp/language_servers/pyright_server.py +++ b/src/solidlsp/language_servers/pyright_server.py @@ -7,6 +7,7 @@ import os import pathlib import re import threading +from typing import cast from overrides import override @@ -56,7 +57,7 @@ class PyrightServer(SolidLanguageServer): Returns the initialize params for the Pyright Language Server. """ # Create basic initialization parameters - initialize_params: InitializeParams = { # type: ignore + initialize_params = { # type: ignore "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": pathlib.Path(repository_absolute_path).as_uri(), @@ -107,9 +108,9 @@ class PyrightServer(SolidLanguageServer): ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the Pyright Language Server and waits for initial workspace analysis to complete. @@ -127,13 +128,13 @@ class PyrightServer(SolidLanguageServer): ``` """ - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: """ Monitor Pyright's log messages to detect when initial analysis is complete. Pyright logs "Found X source files" when it finishes scanning the workspace. @@ -149,7 +150,7 @@ class PyrightServer(SolidLanguageServer): self.analysis_complete.set() self.completions_available.set() - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: """ Also listen for experimental/serverStatus as a backup signal """ diff --git a/src/solidlsp/language_servers/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer.py index 48bae28a..4c448990 100644 --- a/src/solidlsp/language_servers/rust_analyzer.py +++ b/src/solidlsp/language_servers/rust_analyzer.py @@ -7,6 +7,7 @@ import os import pathlib import stat import threading +from typing import cast from overrides import override @@ -580,14 +581,14 @@ class RustAnalyzer(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the Rust Analyzer Language Server """ - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": @@ -595,24 +596,24 @@ class RustAnalyzer(SolidLanguageServer): self.resolve_main_method_available.set() return - def lang_status_handler(params): + def lang_status_handler(params: dict) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": self.service_ready_event.set() - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: if params["quiescent"] == True: self.server_ready.set() - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", register_capability_handler) @@ -633,7 +634,7 @@ class RustAnalyzer(SolidLanguageServer): logging.INFO, ) init_response = self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 + assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore assert "completionProvider" in init_response["capabilities"] assert init_response["capabilities"]["completionProvider"] == { "resolveProvider": True, diff --git a/src/solidlsp/language_servers/solargraph.py b/src/solidlsp/language_servers/solargraph.py index 56a1b7f7..73c46ba0 100644 --- a/src/solidlsp/language_servers/solargraph.py +++ b/src/solidlsp/language_servers/solargraph.py @@ -10,6 +10,7 @@ import pathlib import stat import subprocess import threading +from typing import cast from overrides import override @@ -71,7 +72,7 @@ class Solargraph(SolidLanguageServer): # Check if Ruby is installed try: result = subprocess.run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path) - ruby_version = result.stdout.strip() + ruby_version = str(result.stdout).strip() logger.log(f"Ruby version: {ruby_version}", logging.INFO) except subprocess.CalledProcessError as e: raise RuntimeError(f"Error checking for Ruby installation: {e.stderr}") from e @@ -81,16 +82,16 @@ class Solargraph(SolidLanguageServer): # Check if solargraph is installed try: result = subprocess.run( - ["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path + ["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path # type: ignore ) - if result.stdout.strip() == "false": + if str(result.stdout).strip() == "false": logger.log("Installing Solargraph...", logging.INFO) subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path) # Get the gem executable path directly - result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path) + result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path) # type: ignore gem_path = result.stdout.strip() - bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin") + bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin") # type: ignore executable_path = os.path.join(bin_dir, "solargraph") if not os.path.exists(executable_path): @@ -122,14 +123,14 @@ class Solargraph(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the Solargraph Language Server for Ruby """ - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": @@ -137,20 +138,20 @@ class Solargraph(SolidLanguageServer): self.resolve_main_method_available.set() return - def lang_status_handler(params): + def lang_status_handler(params: dict) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": self.service_ready_event.set() - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) self.server.on_request("client/registerCapability", register_capability_handler) diff --git a/src/solidlsp/language_servers/terraform_ls.py b/src/solidlsp/language_servers/terraform_ls.py index e40d9af1..1c5339ac 100644 --- a/src/solidlsp/language_servers/terraform_ls.py +++ b/src/solidlsp/language_servers/terraform_ls.py @@ -3,6 +3,7 @@ import os import shutil import stat import threading +from typing import cast from overrides import override @@ -27,7 +28,7 @@ class TerraformLS(SolidLanguageServer): return super().is_ignored_dirname(dirname) or dirname in [".terraform", "terraform.tfstate.d"] @staticmethod - def _ensure_tf_command_available(logger: LanguageServerLogger): + def _ensure_tf_command_available(logger: LanguageServerLogger) -> None: logger.log("Starting terraform version detection...", logging.DEBUG) # 1. Try to find terraform using shutil.which @@ -141,7 +142,7 @@ class TerraformLS(SolidLanguageServer): Returns the initialize params for the Terraform Language Server. """ root_uri = PathUtils.path_to_uri(repository_absolute_path) - return { + result = { "processId": os.getpid(), "locale": "en", "rootPath": repository_absolute_path, @@ -166,17 +167,18 @@ class TerraformLS(SolidLanguageServer): } ], } + return cast(InitializeParams, result) - def _start_server(self): + def _start_server(self) -> None: """Start terraform-ls server process""" - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - def do_nothing(params): + def do_nothing(params: dict) -> None: return self.server.on_request("client/registerCapability", register_capability_handler) diff --git a/src/solidlsp/language_servers/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server.py index 9709cc8f..21af4f15 100644 --- a/src/solidlsp/language_servers/typescript_language_server.py +++ b/src/solidlsp/language_servers/typescript_language_server.py @@ -8,6 +8,7 @@ import pathlib import shutil import threading from time import sleep +from typing import Any, cast from overrides import override @@ -19,6 +20,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from ..lsp_protocol_handler import lsp_types from .common import RuntimeDependency, RuntimeDependencyCollection # Platform-specific imports @@ -26,9 +28,9 @@ if os.name != "nt": # Unix-like systems import pwd else: # Dummy pwd module for Windows - class pwd: + class pwd: # type: ignore @staticmethod - def getpwuid(uid): + def getpwuid(uid: Any) -> Any: return type("obj", (), {"pw_name": os.environ.get("USERNAME", "unknown")})() @@ -170,9 +172,9 @@ class TypeScriptLanguageServer(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the TypeScript Language Server, waits for the server to be ready and yields the LanguageServer instance. @@ -186,7 +188,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): # LanguageServer has been shutdown """ - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": @@ -196,16 +198,16 @@ class TypeScriptLanguageServer(SolidLanguageServer): # self.resolve_main_method_available.set() return - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: """ Also listen for experimental/serverStatus as a backup signal """ @@ -249,7 +251,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): @override # For some reason, the LS may need longer to process this, so we just retry - def _send_references_request(self, relative_file_path: str, line: int, column: int): + def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None: # TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is # despite the LS having processed requests already. I don't know what causes this, but sleeping # one second helps. It may be that sleeping only once is enough but that's hard to reliably test. diff --git a/src/solidlsp/language_servers/vts_language_server.py b/src/solidlsp/language_servers/vts_language_server.py index c8596b89..1fc42736 100644 --- a/src/solidlsp/language_servers/vts_language_server.py +++ b/src/solidlsp/language_servers/vts_language_server.py @@ -10,6 +10,7 @@ import pathlib import shutil import threading from time import sleep +from typing import cast from overrides import override @@ -21,6 +22,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from ..lsp_protocol_handler import lsp_types from .common import RuntimeDependency, RuntimeDependencyCollection @@ -145,9 +147,9 @@ class VtsLanguageServer(SolidLanguageServer): } ], } - return initialize_params + return cast(InitializeParams, initialize_params) - def _start_server(self): + def _start_server(self) -> None: """ Starts the VTS Language Server, waits for the server to be ready and yields the LanguageServer instance. @@ -161,30 +163,30 @@ class VtsLanguageServer(SolidLanguageServer): # LanguageServer has been shutdown """ - def register_capability_handler(params): + def register_capability_handler(params: dict) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": self.initialize_searcher_command_available.set() return - def execute_client_command_handler(params): + def execute_client_command_handler(params: dict) -> list: return [] - def workspace_configuration_handler(params): + def workspace_configuration_handler(params: dict) -> list[dict] | dict: # VTS may request workspace configuration # Return empty configuration for each requested item if "items" in params: return [{}] * len(params["items"]) return {} - def do_nothing(params): + def do_nothing(params: dict) -> None: return - def window_log_message(msg): + def window_log_message(msg: dict) -> None: self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - def check_experimental_status(params): + def check_experimental_status(params: dict) -> None: """ Also listen for experimental/serverStatus as a backup signal """ @@ -233,6 +235,6 @@ class VtsLanguageServer(SolidLanguageServer): # VTS may need longer to process references, so we include a sleep similar to TypeScript LS @override - def _send_references_request(self, relative_file_path: str, line: int, column: int): + def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None: sleep(1) return super()._send_references_request(relative_file_path, line, column) diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 415f4139..5a2b0d6a 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -14,7 +14,7 @@ from collections.abc import Iterator from contextlib import contextmanager from copy import copy from pathlib import Path, PurePath -from typing import Self, Union, cast +from typing import Any, Self, Union, cast import pathspec @@ -72,7 +72,7 @@ class LSPFileBuffer: content_hash: str = "" - def __post_init__(self): + def __post_init__(self) -> None: self.content_hash = hashlib.md5(self.contents.encode("utf-8")).hexdigest() @@ -227,7 +227,7 @@ class SolidLanguageServer(ABC): ls = TerraformLS(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings) else: - logger.log(f"Language {config.code_language} is not supported", logging.ERROR) + logger.log(f"Language {config.code_language} is not supported", logging.ERROR) # type: ignore raise LanguageServerException(f"Language {config.code_language} is not supported") ls.set_request_timeout(timeout) @@ -280,11 +280,11 @@ class SolidLanguageServer(ABC): self.completions_available = threading.Event() if config.trace_lsp_communication: - def logging_fn(source: str, target: str, msg: StringDict | str): + def logging_fn(source: str, target: str, msg: StringDict | str) -> None: self.logger.log(f"LSP: {source} -> {target}: {str(msg)[:90]}...", self.logger.logger.level) else: - logging_fn = None + logging_fn = None # type: ignore # cmd is obtained from the child classes, which provide the language specific command to start the language server # LanguageServerHandler provides the functionality to start the language server and communicate with it @@ -366,7 +366,7 @@ class SolidLanguageServer(ABC): return match_path(relative_path, self.get_ignore_spec(), root_path=self.repository_root_path) - def _shutdown(self, timeout: float = 5.0): + def _shutdown(self, timeout: float = 5.0) -> None: """ A robust shutdown process designed to terminate cleanly on all platforms, including Windows, by explicitly closing all I/O pipes. @@ -377,6 +377,9 @@ class SolidLanguageServer(ABC): self.logger.log(f"Initiating final robust shutdown with a {timeout}s timeout...", logging.INFO) process = self.server.process + if process is None: + self.logger.log("Server process is None, cannot shutdown.", logging.DEBUG) + return # --- Main Shutdown Logic --- # Stage 1: Graceful Termination Request @@ -394,7 +397,7 @@ class SolidLanguageServer(ABC): else: self.logger.log("LSP shutdown request completed.", logging.DEBUG) - if process.stdin and not process.stdin.is_closing(): + if process.stdin and not process.stdin.closed: process.stdin.close() self.logger.log("Stage 1 shutdown complete.", logging.DEBUG) except Exception as e: @@ -428,12 +431,12 @@ class SolidLanguageServer(ABC): yield self self.stop() - def _start_server_process(self) -> None: + def _start_server_process(self) -> Any | None: self.server_started = True - self._start_server() + return self._start_server() @abstractmethod - def _start_server(self): + def _start_server(self) -> None: pass @contextmanager @@ -468,7 +471,7 @@ class SolidLanguageServer(ABC): self.server.notify.did_open_text_document( { - LSPConstants.TEXT_DOCUMENT: { + LSPConstants.TEXT_DOCUMENT: { # type: ignore LSPConstants.URI: uri, LSPConstants.LANGUAGE_ID: self.language_id, LSPConstants.VERSION: 0, @@ -482,7 +485,7 @@ class SolidLanguageServer(ABC): if self.open_file_buffers[uri].ref_count == 0: self.server.notify.did_close_text_document( { - LSPConstants.TEXT_DOCUMENT: { + LSPConstants.TEXT_DOCUMENT: { # type: ignore LSPConstants.URI: uri, } } @@ -519,7 +522,7 @@ class SolidLanguageServer(ABC): file_buffer.contents = new_contents self.server.notify.did_change_text_document( { - LSPConstants.TEXT_DOCUMENT: { + LSPConstants.TEXT_DOCUMENT: { # type: ignore LSPConstants.VERSION: file_buffer.version, LSPConstants.URI: file_buffer.uri, }, @@ -566,7 +569,7 @@ class SolidLanguageServer(ABC): file_buffer.contents = new_contents self.server.notify.did_change_text_document( { - LSPConstants.TEXT_DOCUMENT: { + LSPConstants.TEXT_DOCUMENT: { # type: ignore LSPConstants.VERSION: file_buffer.version, LSPConstants.URI: file_buffer.uri, }, @@ -618,23 +621,23 @@ class SolidLanguageServer(ABC): for item in response: assert isinstance(item, dict) if LSPConstants.URI in item and LSPConstants.RANGE in item: - new_item: ls_types.Location = {} + new_item: dict = {} new_item.update(item) new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - ret.append(ls_types.Location(new_item)) + ret.append(ls_types.Location(new_item)) # type: ignore elif ( LSPConstants.ORIGIN_SELECTION_RANGE in item and LSPConstants.TARGET_URI in item and LSPConstants.TARGET_RANGE in item and LSPConstants.TARGET_SELECTION_RANGE in item ): - new_item: ls_types.Location = {} - new_item["uri"] = item[LSPConstants.TARGET_URI] + new_item = {} + new_item["uri"] = item[LSPConstants.TARGET_URI] # type: ignore new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE] - ret.append(ls_types.Location(**new_item)) + new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE] # type: ignore + ret.append(ls_types.Location(**new_item)) # type: ignore else: assert False, f"Unexpected response from Language Server: {item}" elif isinstance(response, dict): @@ -642,11 +645,11 @@ class SolidLanguageServer(ABC): assert LSPConstants.URI in response assert LSPConstants.RANGE in response - new_item: ls_types.Location = {} + new_item = {} new_item.update(response) new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - ret.append(ls_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) # type: ignore elif response is None: # Some language servers return None when they cannot find a definition # This is expected for certain symbol types like generics or types with incomplete information @@ -709,7 +712,7 @@ class SolidLanguageServer(ABC): assert LSPConstants.URI in item assert LSPConstants.RANGE in item - abs_path = PathUtils.uri_to_path(item[LSPConstants.URI]) + abs_path = PathUtils.uri_to_path(item[LSPConstants.URI]) # type: ignore if not Path(abs_path).is_relative_to(self.repository_root_path): self.logger.log( "Found a reference in a path outside the repository, probably the LS is parsing things in installed packages or in the standardlib! " @@ -723,11 +726,11 @@ class SolidLanguageServer(ABC): self.logger.log(f"Ignoring reference in {rel_path} since it should be ignored", logging.DEBUG) continue - new_item: ls_types.Location = {} + new_item: dict = {} new_item.update(item) new_item["absolutePath"] = str(abs_path) new_item["relativePath"] = str(rel_path) - ret.append(ls_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) # type: ignore return ret @@ -786,19 +789,19 @@ class SolidLanguageServer(ABC): response: list[LSPTypes.CompletionItem] | LSPTypes.CompletionList | None = None num_retries = 0 - while response is None or (response["isIncomplete"] and num_retries < 30): + while response is None or (response["isIncomplete"] and num_retries < 30): # type: ignore self.completions_available.wait() - response: list[LSPTypes.CompletionItem] | LSPTypes.CompletionList | None = self.server.send.completion(completion_params) + response = self.server.send.completion(completion_params) if isinstance(response, list): response = {"items": response, "isIncomplete": False} num_retries += 1 # TODO: Understand how to appropriately handle `isIncomplete` - if response is None or (response["isIncomplete"] and not (allow_incomplete)): + if response is None or (response["isIncomplete"] and not allow_incomplete): # type: ignore return [] if "items" in response: - response = response["items"] + response = response["items"] # type: ignore response = cast(list[LSPTypes.CompletionItem], response) @@ -816,8 +819,8 @@ class SolidLanguageServer(ABC): if "label" in item: completion_item["completionText"] = item["label"] - completion_item["kind"] = item["kind"] - elif "insertText" in item: + completion_item["kind"] = item["kind"] # type: ignore + elif "insertText" in item: # type: ignore completion_item["completionText"] = item["insertText"] completion_item["kind"] = item["kind"] elif "textEdit" in item and "newText" in item["textEdit"]: @@ -844,7 +847,7 @@ class SolidLanguageServer(ABC): else: assert False - completion_item = ls_types.CompletionItem(**completion_item) + completion_item = ls_types.CompletionItem(**completion_item) # type: ignore completions_list.append(completion_item) return [json.loads(json_repr) for json_repr in set(json.dumps(item, sort_keys=True) for item in completions_list)] @@ -899,7 +902,7 @@ class SolidLanguageServer(ABC): logging.DEBUG, ) - def turn_item_into_symbol_with_children(item: GenericDocumentSymbol): + def turn_item_into_symbol_with_children(item: GenericDocumentSymbol) -> None: item = cast(ls_types.UnifiedSymbolInformation, item) absolute_path = os.path.join(self.repository_root_path, relative_file_path) @@ -916,9 +919,9 @@ class SolidLanguageServer(ABC): item["location"] = tree_location location = item["location"] if "absolutePath" not in location: - location["absolutePath"] = absolute_path + location["absolutePath"] = absolute_path # type: ignore if "relativePath" not in location: - location["relativePath"] = relative_file_path + location["relativePath"] = relative_file_path # type: ignore if include_body: item["body"] = self.retrieve_symbol_body(item) # handle missing selectionRange @@ -927,16 +930,16 @@ class SolidLanguageServer(ABC): item["selectionRange"] = item["range"] else: item["selectionRange"] = item["location"]["range"] - children = item.get(LSPConstants.CHILDREN, []) - for child in children: + children = item.get(LSPConstants.CHILDREN, []) # type: ignore + for child in children: # type: ignore child["parent"] = item - item[LSPConstants.CHILDREN] = children + item[LSPConstants.CHILDREN] = children # type: ignore flat_all_symbol_list: list[ls_types.UnifiedSymbolInformation] = [] root_nodes: list[ls_types.UnifiedSymbolInformation] = [] for root_item in response: if "range" not in root_item and "location" not in root_item: - if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]: + if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]: # type: ignore ... # mutation is more convenient than creating a new dict, @@ -959,7 +962,7 @@ class SolidLanguageServer(ABC): l: list[ls_types.UnifiedSymbolInformation] = [] turn_item_into_symbol_with_children(node) assert LSPConstants.CHILDREN in node - children = node[LSPConstants.CHILDREN] + children = node[LSPConstants.CHILDREN] # type: ignore l.append(node) for child in children: l.extend(visit_tree_nodes_and_build_tree_repr(child)) @@ -1082,10 +1085,10 @@ class SolidLanguageServer(ABC): package_symbol["children"].append(file_symbol) # TODO: Not sure if this is actually still needed given recent changes to relative path handling - def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]): + def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]) -> None: for node in nodes: if "location" in node and "relativePath" in node["location"]: - path = Path(node["location"]["relativePath"]) + path = Path(node["location"]["relativePath"]) # type: ignore if path.is_absolute(): try: path = path.relative_to(self.repository_root_path) @@ -1125,7 +1128,7 @@ class SolidLanguageServer(ABC): result: dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]] = defaultdict(list) # Helper function to process a symbol and its children - def process_symbol(symbol: ls_types.UnifiedSymbolInformation): + def process_symbol(symbol: ls_types.UnifiedSymbolInformation) -> None: if symbol["kind"] == ls_types.SymbolKind.File: # For file symbols, process their children (top-level symbols) for child in symbol["children"]: @@ -1209,7 +1212,7 @@ class SolidLanguageServer(ABC): assert isinstance(response, dict) - return ls_types.Hover(**response) + return ls_types.Hover(**response) # type: ignore def retrieve_symbol_body(self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation) -> str: """ @@ -1217,18 +1220,18 @@ class SolidLanguageServer(ABC): """ existing_body = symbol.get("body", None) if existing_body: - return existing_body + return str(existing_body) assert "location" in symbol - symbol_start_line = symbol["location"]["range"]["start"]["line"] - symbol_end_line = symbol["location"]["range"]["end"]["line"] - assert "relativePath" in symbol["location"] - symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"]) + symbol_start_line = symbol["location"]["range"]["start"]["line"] # type: ignore + symbol_end_line = symbol["location"]["range"]["end"]["line"] # type: ignore + assert "relativePath" in symbol["location"] # type: ignore + symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"]) # type: ignore symbol_lines = symbol_file.split("\n") symbol_body = "\n".join(symbol_lines[symbol_start_line : symbol_end_line + 1]) # remove leading indentation - symbol_start_column = symbol["location"]["range"]["start"]["character"] + symbol_start_column = symbol["location"]["range"]["start"]["character"] # type: ignore symbol_body = symbol_body[symbol_start_column:] return symbol_body @@ -1277,6 +1280,7 @@ class SolidLanguageServer(ABC): incoming_symbol = None for ref in references: ref_path = ref["relativePath"] + assert ref_path is not None ref_line = ref["range"]["start"]["line"] ref_col = ref["range"]["start"]["character"] @@ -1300,6 +1304,7 @@ class SolidLanguageServer(ABC): ref_text = file_data.contents.split("\n")[ref_line] if "." in ref_text: containing_symbol_name = ref_text.split(".")[0] + assert ref_path is not None all_symbols, _ = self.request_document_symbols(ref_path) for symbol in all_symbols: if symbol["name"] == containing_symbol_name and symbol["kind"] == ls_types.SymbolKind.Variable: @@ -1506,7 +1511,7 @@ class SolidLanguageServer(ABC): return symbol["parent"] assert "location" in symbol, f"Symbol {symbol} has no location and no parent attribute" return self.request_containing_symbol( - symbol["location"]["relativePath"], + symbol["location"]["relativePath"], # type: ignore symbol["location"]["range"]["start"]["line"], symbol["location"]["range"]["start"]["character"], strict=True, @@ -1547,6 +1552,7 @@ class SolidLanguageServer(ABC): # Use the first definition location definition = definitions[0] def_path = definition["relativePath"] + assert def_path is not None def_line = definition["range"]["start"]["line"] def_col = definition["range"]["start"]["character"] @@ -1562,7 +1568,7 @@ class SolidLanguageServer(ABC): """ return Path(self.repository_root_path) / ".serena" / "cache" / self.language_id / "document_symbols_cache_v23-06-25.pkl" - def save_cache(self): + def save_cache(self) -> None: with self._cache_lock: if not self._cache_has_changed: self.logger.log("No changes to document symbols cache, skipping save", logging.DEBUG) @@ -1581,7 +1587,7 @@ class SolidLanguageServer(ABC): logging.ERROR, ) - def load_cache(self): + def load_cache(self) -> None: if not self.cache_path.exists(): return @@ -1622,7 +1628,7 @@ class SolidLanguageServer(ABC): assert LSPConstants.KIND in item assert LSPConstants.LOCATION in item - ret.append(ls_types.UnifiedSymbolInformation(**item)) + ret.append(ls_types.UnifiedSymbolInformation(**item)) # type: ignore return ret diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index efb55a87..2d4ebb92 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -120,7 +120,7 @@ class LanguageServerConfig: """Paths, dirs or glob-like patterns. The matching will follow the same logic as for .gitignore entries""" @classmethod - def from_dict(cls, env: dict): + def from_dict(cls, env: dict) -> Self: """ Create a MultilspyConfig instance from a dictionary """ diff --git a/src/solidlsp/ls_handler.py b/src/solidlsp/ls_handler.py index 731952f3..ab50a938 100644 --- a/src/solidlsp/ls_handler.py +++ b/src/solidlsp/ls_handler.py @@ -46,7 +46,7 @@ class Request: return self.error is not None def __init__(self) -> None: - self._result_queue = Queue() + self._result_queue: Queue[Request.Result] = Queue() def on_result(self, params: PayloadLike) -> None: self._result_queue.put(Request.Result(payload=params)) @@ -101,22 +101,22 @@ class SolidLanguageServerHandler: self, process_launch_info: ProcessLaunchInfo, logger: Callable[[str, str, StringDict | str], None] | None = None, - start_independent_lsp_process=True, + start_independent_lsp_process: bool = True, request_timeout: float | None = None, ) -> None: self.send = LanguageServerRequest(self.send_request) self.notify = LspNotification(self.send_notification) self.process_launch_info = process_launch_info - self.process = None + self.process: subprocess.Popen[bytes] | None = None self._received_shutdown = False self.request_id = 1 self._response_handlers: dict[Any, Request] = {} - self.on_request_handlers = {} - self.on_notification_handlers = {} + self.on_request_handlers: dict[str, Callable[[Any], Any]] = {} + self.on_notification_handlers: dict[str, Callable[[Any], None]] = {} self.logger = logger - self.tasks = {} + self.tasks: dict[int, Any] = {} self.task_counter = 0 self.loop = None self.start_independent_lsp_process = start_independent_lsp_process @@ -170,7 +170,7 @@ class SolidLanguageServerHandler: if self.process.returncode is not None: log.error("Language server has already terminated/could not be started") # Process has already terminated - stderr_data = self.process.stderr.read() + stderr_data = self.process.stderr.read() if self.process.stderr else b"" error_message = stderr_data.decode("utf-8", errors="replace") raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}") @@ -195,7 +195,7 @@ class SolidLanguageServerHandler: if process: self._cleanup_process(process) - def _cleanup_process(self, process): + def _cleanup_process(self, process: subprocess.Popen[bytes]) -> None: """Clean up a process: close stdin, terminate/kill process, close stdout/stderr.""" # Close stdin first to prevent deadlocks # See: https://bugs.python.org/issue35539 @@ -212,7 +212,7 @@ class SolidLanguageServerHandler: self._safely_close_pipe(process.stdout) self._safely_close_pipe(process.stderr) - def _safely_close_pipe(self, pipe): + def _safely_close_pipe(self, pipe: Any) -> None: """Safely close a pipe, ignoring any exceptions.""" if pipe: try: @@ -220,7 +220,7 @@ class SolidLanguageServerHandler: except Exception: pass - def _terminate_or_kill_process(self, process): + def _terminate_or_kill_process(self, process: subprocess.Popen[bytes]) -> None: """Try to terminate the process gracefully, then forcefully if necessary.""" # First try to terminate the process tree gracefully self._signal_process_tree(process, terminate=True) @@ -240,7 +240,7 @@ class SolidLanguageServerHandler: pass """ - def _signal_process_tree(self, process, terminate=True): + def _signal_process_tree(self, process: subprocess.Popen[bytes], terminate: bool = True) -> None: """Send signal (terminate or kill) to the process and all its children.""" signal_method = "terminate" if terminate else "kill" @@ -300,7 +300,7 @@ class SolidLanguageServerHandler: self.logger("client", "logger", message) @staticmethod - def _read_bytes_from_process(process, stream, num_bytes): + def _read_bytes_from_process(process: subprocess.Popen[bytes], stream: Any, num_bytes: int) -> bytes: """Read exactly num_bytes from process stdout""" if process.poll() is not None: # Process has terminated, check if we can still read @@ -457,13 +457,13 @@ class SolidLanguageServerHandler: self.logger("client", "logger", f"Failed to write to stdin: {e}") return - def on_request(self, method: str, cb) -> None: + def on_request(self, method: str, cb: Callable[[Any], Any]) -> None: """ Register the callback function to handle requests from the server to the client for the given method """ self.on_request_handlers[method] = cb - def on_notification(self, method: str, cb) -> None: + def on_notification(self, method: str, cb: Callable[[Any], None]) -> None: """ Register the callback function to handle notifications from the server to the client for the given method """ diff --git a/src/solidlsp/ls_request.py b/src/solidlsp/ls_request.py index 209a3f04..d76ffa5d 100644 --- a/src/solidlsp/ls_request.py +++ b/src/solidlsp/ls_request.py @@ -1,10 +1,11 @@ -from typing import Union +from collections.abc import Callable +from typing import Any, Union from solidlsp.lsp_protocol_handler import lsp_types class LanguageServerRequest: - def __init__(self, send_request): + def __init__(self, send_request: Callable[[str, Any], Any]) -> None: self.send_request = send_request def implementation(self, params: lsp_types.ImplementationParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: @@ -218,7 +219,7 @@ class LanguageServerRequest: server. The only notification that is sent after a shutdown request is the exit event. """ - return self.send_request("shutdown") + return self.send_request("shutdown", None) def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None: """A document will save request is sent from the client to the server before diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index 5110fe86..aa6b4885 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -125,10 +125,15 @@ class PathUtils: from urllib.parse import unquote, urlparse from urllib.request import url2pathname except ImportError: - # backwards compatibility - from urllib import unquote, url2pathname + # backwards compatibility (Python 2) + from urllib.parse import unquote as unquote_py2 + from urllib.request import url2pathname as url2pathname_py2 - from urlparse import urlparse + from urlparse import urlparse as urlparse_py2 + + unquote = unquote_py2 + url2pathname = url2pathname_py2 + urlparse = urlparse_py2 parsed = urlparse(uri) host = f"{os.path.sep}{os.path.sep}{parsed.netloc}{os.path.sep}" path = os.path.normpath(os.path.join(host, url2pathname(unquote(parsed.path)))) @@ -290,7 +295,7 @@ class PlatformUtils: raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}") @staticmethod - def _determine_windows_machine_type(): + def _determine_windows_machine_type() -> str: import ctypes from ctypes import wintypes @@ -317,7 +322,7 @@ class PlatformUtils: _anonymous_ = ("u",) sys_info = SYSTEM_INFO() - ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info)) + ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info)) # type: ignore arch_map = { 9: "AMD64", diff --git a/src/solidlsp/lsp_protocol_handler/lsp_requests.py b/src/solidlsp/lsp_protocol_handler/lsp_requests.py index 383e4566..78f308b2 100644 --- a/src/solidlsp/lsp_protocol_handler/lsp_requests.py +++ b/src/solidlsp/lsp_protocol_handler/lsp_requests.py @@ -29,13 +29,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import Union +from typing import Any, Union from solidlsp.lsp_protocol_handler import lsp_types class LspRequest: - def __init__(self, send_request): + def __init__(self, send_request: Any) -> None: self.send_request = send_request async def implementation( @@ -421,7 +421,7 @@ class LspRequest: class LspNotification: - def __init__(self, send_notification): + def __init__(self, send_notification: Any) -> None: self.send_notification = send_notification def did_change_workspace_folders(self, params: lsp_types.DidChangeWorkspaceFoldersParams) -> None: diff --git a/src/solidlsp/lsp_protocol_handler/server.py b/src/solidlsp/lsp_protocol_handler/server.py index 0cb59490..24bc2bcd 100644 --- a/src/solidlsp/lsp_protocol_handler/server.py +++ b/src/solidlsp/lsp_protocol_handler/server.py @@ -95,7 +95,7 @@ class StopLoopException(Exception): pass -def create_message(payload: PayloadLike): +def create_message(payload: PayloadLike) -> tuple[bytes, bytes, bytes]: body = json.dumps(payload, check_circular=False, ensure_ascii=False, separators=(",", ":")).encode(ENCODING) return ( f"Content-Length: {len(body)}\r\n".encode(ENCODING), @@ -118,5 +118,5 @@ def content_length(line: bytes) -> int | None: try: return int(value) except ValueError: - raise ValueError(f"Invalid Content-Length header: {value}") + raise ValueError(f"Invalid Content-Length header: {value!r}") return None diff --git a/src/solidlsp/settings.py b/src/solidlsp/settings.py index 0b956bff..3b4dbff0 100644 --- a/src/solidlsp/settings.py +++ b/src/solidlsp/settings.py @@ -11,10 +11,10 @@ from dataclasses import dataclass class SolidLSPSettings: solidlsp_dir: str = str(pathlib.Path.home() / ".solidlsp") - def __post_init__(self): + def __post_init__(self) -> None: os.makedirs(str(self.solidlsp_dir), exist_ok=True) os.makedirs(str(self.ls_resources_dir), exist_ok=True) @property - def ls_resources_dir(self): + def ls_resources_dir(self) -> str: return os.path.join(str(self.solidlsp_dir), "language_servers", "static") From d1afa040c40b7ffa6870715f319c640d1f558bf7 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 27 Jul 2025 23:56:35 +0200 Subject: [PATCH 002/253] Add type annotations --- src/solidlsp/language_servers/common.py | 4 ++-- src/solidlsp/ls_exceptions.py | 4 ++-- src/solidlsp/ls_utils.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/solidlsp/language_servers/common.py b/src/solidlsp/language_servers/common.py index c9ff085c..b3f4529a 100644 --- a/src/solidlsp/language_servers/common.py +++ b/src/solidlsp/language_servers/common.py @@ -78,7 +78,7 @@ class RuntimeDependencyCollection: else: import pwd - kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name + kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name # type: ignore log.info("Running command '%s' in '%s'", command, cwd) completed_process = subprocess.run( command, @@ -88,7 +88,7 @@ class RuntimeDependencyCollection: stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs, - ) + ) # type: ignore if completed_process.returncode != 0: log.warning("Command '%s' failed with return code %d", command, completed_process.returncode) log.warning("Command output:\n%s", completed_process.stdout) diff --git a/src/solidlsp/ls_exceptions.py b/src/solidlsp/ls_exceptions.py index 7c03cfe5..ac5a42a7 100644 --- a/src/solidlsp/ls_exceptions.py +++ b/src/solidlsp/ls_exceptions.py @@ -4,7 +4,7 @@ This module contains the exceptions raised by the framework. class SolidLSPException(Exception): - def __init__(self, message: str, cause: Exception | None = None): + def __init__(self, message: str, cause: Exception | None = None) -> None: """ Initializes the exception with the given message. @@ -17,7 +17,7 @@ class SolidLSPException(Exception): self.cause = cause super().__init__(message) - def is_language_server_terminated(self): + def is_language_server_terminated(self) -> bool: """ :return: True if the exception is caused by the language server having terminated as indicated by the causing exception being an instance of LanguageServerTerminatedException. diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index d25e0848..1dee3c25 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -253,7 +253,7 @@ class PlatformId(str, Enum): LINUX_MUSL_x64 = "linux-musl-x64" LINUX_MUSL_arm64 = "linux-musl-arm64" - def is_windows(self): + def is_windows(self) -> bool: return self.value.startswith("win") From 0824118a58d5776d9070c1ad35289307adce35a3 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 28 Jul 2025 00:07:08 +0200 Subject: [PATCH 003/253] Remove obsolete return values --- src/solidlsp/ls.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index a2abbe73..ce779b73 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -14,7 +14,7 @@ from collections.abc import Iterator from contextlib import contextmanager from copy import copy from pathlib import Path, PurePath -from typing import Any, Self, Union, cast +from typing import Self, Union, cast import pathspec @@ -309,7 +309,6 @@ class SolidLanguageServer(ABC): # Create a pathspec matcher from the processed patterns self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, processed_patterns) - self._server_context = None self._request_timeout: float | None = None def set_request_timeout(self, timeout: float | None) -> None: @@ -431,9 +430,9 @@ class SolidLanguageServer(ABC): yield self self.stop() - def _start_server_process(self) -> Any | None: + def _start_server_process(self) -> None: self.server_started = True - return self._start_server() + self._start_server() @abstractmethod def _start_server(self) -> None: @@ -1684,7 +1683,7 @@ class SolidLanguageServer(ABC): f"Starting language server with language {self.language_server.language} for {self.language_server.repository_root_path}", logging.INFO, ) - self._server_context = self._start_server_process() + self._start_server_process() return self def stop(self, shutdown_timeout: float = 2.0) -> None: From d777927716b7ffaa5b4793c5d881c73a5d747171 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 28 Jul 2025 00:09:11 +0200 Subject: [PATCH 004/253] Format imports --- src/solidlsp/ls_request.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/solidlsp/ls_request.py b/src/solidlsp/ls_request.py index 52be9c96..cacd2872 100644 --- a/src/solidlsp/ls_request.py +++ b/src/solidlsp/ls_request.py @@ -1,6 +1,4 @@ from typing import TYPE_CHECKING, Any, Union -from collections.abc import Callable -from typing import Any, Union from solidlsp.lsp_protocol_handler import lsp_types From 25da7a505fbbfe4f1931537a2c3e75bf921d0c05 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Thu, 16 Oct 2025 09:24:30 +0100 Subject: [PATCH 005/253] Add rename_symbol method (#660) Tested through a snapshot test on python. Calling the tool with language servers not supporting renaming will raise an error Co-authored-by: Don Syme --- src/serena/code_editor.py | 74 ++++++++++++- src/serena/tools/symbol_tools.py | 28 +++++ src/solidlsp/ls.py | 46 ++++++++ src/solidlsp/ls_types.py | 61 +++++++++++ .../__snapshots__/test_symbol_editing.ambr | 101 ++++++++++++++++++ test/serena/test_symbol_editing.py | 56 ++++++++-- 6 files changed, 353 insertions(+), 13 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 6fe41da1..ef00970b 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -4,12 +4,13 @@ import os from abc import ABC, abstractmethod from collections.abc import Iterable, Iterator, Reversible from contextlib import contextmanager -from typing import TYPE_CHECKING, Generic, Optional, TypeVar +from typing import TYPE_CHECKING, Generic, Optional, TypeVar, cast from serena.symbol import JetBrainsSymbol, LanguageServerSymbol, LanguageServerSymbolRetriever, PositionInFile, Symbol -from solidlsp import SolidLanguageServer +from solidlsp import SolidLanguageServer, ls_types from solidlsp.ls import LSPFileBuffer -from solidlsp.ls_utils import TextUtils +from solidlsp.ls_types import extract_text_edits +from solidlsp.ls_utils import PathUtils, TextUtils from .project import Project from .tools.jetbrains_plugin_client import JetBrainsPluginClient @@ -207,6 +208,17 @@ class CodeEditor(Generic[TSymbol], ABC): with self._edited_file_context(relative_file_path) as edited_file: edited_file.delete_text_between_positions(start_pos, end_pos) + @abstractmethod + def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> list[str]: + """ + Renames the symbol with the given name throughout the codebase. + + :param name_path: the name path of the symbol to rename + :param relative_file_path: the relative path of the file containing the symbol + :param new_name: the new name for the symbol + :return: list of files that were modified + """ + class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): def __init__(self, symbol_retriever: LanguageServerSymbolRetriever, agent: Optional["SerenaAgent"] = None): @@ -232,6 +244,9 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): def insert_text_at_position(self, pos: PositionInFile, text: str) -> None: self._lang_server.insert_text_at_position(self._relative_path, pos.line, pos.col, text) + def apply_text_edits(self, text_edits: list[ls_types.TextEdit]) -> None: + return self._lang_server.apply_text_edits_to_file(self._relative_path, text_edits) + @contextmanager def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]: with self._lang_server.open_file(relative_path) as file_buffer: @@ -252,6 +267,52 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): ) return symbol_candidates[0] + def _apply_workspace_edit(self, workspace_edit: ls_types.WorkspaceEdit) -> list[str]: + """ + Apply a WorkspaceEdit by making the changes to files. + + :param workspace_edit: The WorkspaceEdit containing the changes to apply + :return: List of relative file paths that were modified + """ + uri_to_edits = extract_text_edits(workspace_edit) + modified_relative_paths = [] + + # Handle the 'changes' format (URI -> list of TextEdits) + for uri, edits in uri_to_edits.items(): + file_path = PathUtils.uri_to_path(uri) + relative_path = os.path.relpath(file_path, self._lang_server.repository_root_path) + modified_relative_paths.append(relative_path) + with self._edited_file_context(relative_path) as edited_file: + edited_file = cast(self.EditedFile, edited_file) + edited_file.apply_text_edits(edits) + return modified_relative_paths + + def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> list[str]: + """ + Renames the symbol with the given name throughout the codebase. + :param name_path: + :param relative_file_path: + :param new_name: + :return: list of files that were modified + """ + symbol = self._find_unique_symbol(name_path, relative_file_path) + if not symbol.location.has_position_in_file(): + raise ValueError(f"Symbol '{name_path}' does not have a valid position in file for renaming") + + # After has_position_in_file check, line and column are guaranteed to be non-None + assert symbol.location.line is not None + assert symbol.location.column is not None + + rename_result = self._lang_server.request_rename_symbol_edit( + relative_file_path=relative_file_path, line=symbol.location.line, column=symbol.location.column, new_name=new_name + ) + if rename_result is None: + raise ValueError( + f"Language server for {self._lang_server.language_id} returned no rename edits for symbol '{name_path}'. " + f"The symbol might not support renaming." + ) + return self._apply_workspace_edit(rename_result) + class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): def __init__(self, project: Project, agent: Optional["SerenaAgent"] = None) -> None: @@ -292,3 +353,10 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): "Their locations are: \n " + json.dumps([s["location"] for s in symbols], indent=2) ) return JetBrainsSymbol(symbols[0], self._project) + + def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> list[str]: + """ + Renames the symbol with the given name throughout the codebase. + Not yet implemented for JetBrains code editor. + """ + raise NotImplementedError("Symbol renaming is not yet supported for JetBrains code editor") diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 14099ef7..7526d20e 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -82,6 +82,7 @@ class FindSymbolTool(Tool, ToolMarkerSymbolicRead): Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). """ + # noinspection PyDefaultArgument def apply( self, name_path: str, @@ -165,6 +166,7 @@ class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead): Finds symbols that reference the symbol at the given location (optionally filtered by type). """ + # noinspection PyDefaultArgument def apply( self, name_path: str, @@ -293,3 +295,29 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerSymbolicEdit): code_editor = self.create_code_editor() code_editor.insert_before_symbol(name_path, relative_file_path=relative_path, body=body) return SUCCESS_RESULT + + +class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit): + """ + Renames a symbol throughout the codebase using language server refactoring capabilities. + """ + + def apply( + self, + name_path: str, + relative_path: str, + new_name: str, + ) -> str: + """ + Renames the symbol with the given `name_path` to `new_name` throughout the entire codebase. + Note: for languages with method overloading, like Java, name_path may have to include a method's + signature to uniquely identify a method. + + :param name_path: name path of the symbol to rename (definitions in the `find_symbol` tool apply) + :param relative_path: the relative path to the file containing the symbol to rename + :param new_name: the new name for the symbol + :return: result summary indicating success or failure + """ + code_editor = self.create_code_editor() + modified_files = code_editor.rename_symbol(name_path, relative_file_path=relative_path, new_name=new_name) + return f"Successfully renamed '{name_path}' to '{new_name}' in {len(modified_files)} file(s)" diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index c91ff55e..29df1ee4 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -1626,6 +1626,52 @@ class SolidLanguageServer(ABC): return ret + def request_rename_symbol_edit( + self, + relative_file_path: str, + line: int, + column: int, + new_name: str, + ) -> ls_types.WorkspaceEdit | None: + """ + Retrieve a WorkspaceEdit for renaming the symbol at the given location to the new name. + Does not apply the edit, just retrieves it. In order to actually rename the symbol, call apply_workspace_edit. + + :param relative_file_path: The relative path to the file containing the symbol + :param line: The 0-indexed line number of the symbol + :param column: The 0-indexed column number of the symbol + :param new_name: The new name for the symbol + :return: A WorkspaceEdit containing the changes needed to rename the symbol, or None if rename is not supported + """ + params = ls_types.RenameParams( + textDocument=ls_types.TextDocumentIdentifier( + uri=pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() + ), + position=ls_types.Position(line=line, character=column), + newName=new_name, + ) + + return self.server.send.rename(params) + + def apply_text_edits_to_file(self, relative_path: str, edits: list[ls_types.TextEdit]) -> None: + """ + Apply a list of text edits to a file. + + :param relative_path: The relative path of the file to edit + :param edits: List of TextEdit dictionaries to apply + """ + with self.open_file(relative_path): + # Sort edits by position (latest first) to avoid position shifts + sorted_edits = sorted(edits, key=lambda e: (e["range"]["start"]["line"], e["range"]["start"]["character"]), reverse=True) + + for edit in sorted_edits: + start_pos = ls_types.Position(line=edit["range"]["start"]["line"], character=edit["range"]["start"]["character"]) + end_pos = ls_types.Position(line=edit["range"]["end"]["line"], character=edit["range"]["end"]["character"]) + + # Delete the old text and insert the new text + self.delete_text_between_positions(relative_path, start_pos, end_pos) + self.insert_text_at_position(relative_path, start_pos["line"], start_pos["character"], edit["newText"]) + def start(self) -> "SolidLanguageServer": """ Starts the language server process and connects to it. Call shutdown when ready. diff --git a/src/solidlsp/ls_types.py b/src/solidlsp/ls_types.py index d8ff8903..4a8de6d4 100644 --- a/src/solidlsp/ls_types.py +++ b/src/solidlsp/ls_types.py @@ -326,6 +326,42 @@ class DiagnosticsSeverity(IntEnum): HINT = 4 +class TextDocumentIdentifier(TypedDict): + """A literal to identify a text document in the client.""" + + uri: DocumentUri + """ The text document's uri. """ + + +class TextEdit(TypedDict): + """A textual edit applicable to a text document.""" + + range: Range + """ The range of the text document to be manipulated. """ + newText: str + """ The string to be inserted. For delete operations use an empty string. """ + + +class WorkspaceEdit(TypedDict): + """A workspace edit represents changes to many resources managed in the workspace.""" + + changes: NotRequired[dict[DocumentUri, list[TextEdit]]] + """ Holds changes to existing resources. """ + documentChanges: NotRequired[list] + """ Document changes array for versioned edits. """ + + +class RenameParams(TypedDict): + """The parameters of a RenameRequest.""" + + textDocument: TextDocumentIdentifier + """ The document to rename. """ + position: Position + """ The position at which this request was sent. """ + newName: str + """ The new name of the symbol. """ + + class Diagnostic(TypedDict): """Diagnostic information for a text document.""" @@ -341,3 +377,28 @@ class Diagnostic(TypedDict): """ The code of the diagnostic. """ source: NotRequired[str] """ The source of the diagnostic, e.g. the name of the tool that produced it. """ + + +def extract_text_edits(workspace_edit: WorkspaceEdit) -> dict[str, list[TextEdit]]: + """ + Extracts the text changes from a WorkspaceEdit object. + + Args: + workspace_edit (WorkspaceEdit): The WorkspaceEdit object to extract text changes from. + + Returns: + dict[str, list[TextEdit]]: A dictionary mapping document URIs to lists of TextEdit objects. + + """ + if "changes" in workspace_edit: + return workspace_edit["changes"] + elif "documentChanges" in workspace_edit: + changes = {} + for change in workspace_edit["documentChanges"]: + if "textDocument" in change and "edits" in change: + uri = change["textDocument"]["uri"] + edits = change["edits"] + changes[uri] = edits + return changes + else: + raise f"Invalid WorkspaceEdit (expected 'changes' or 'documentChanges' key):\n{workspace_edit}" diff --git a/test/serena/__snapshots__/test_symbol_editing.ambr b/test/serena/__snapshots__/test_symbol_editing.ambr index 3278113d..92603c2d 100644 --- a/test/serena/__snapshots__/test_symbol_editing.ambr +++ b/test/serena/__snapshots__/test_symbol_editing.ambr @@ -983,6 +983,107 @@ ''' # --- +# name: test_rename_symbol + ''' + """ + Test module for variable declarations and usage. + + This module tests various types of variable declarations and usages including: + - Module-level variables + - Class-level variables + - Instance variables + - Variable reassignments + """ + + from dataclasses import dataclass, field + + # Module-level variables + module_var = "Initial module value" + + reassignable_module_var = 10 + reassignable_module_var = 20 # Reassigned + + # Module-level variable with type annotation + renamed_typed_module_var: int = 42 + + + # Regular class with class and instance variables + class VariableContainer: + """Class that contains various variables.""" + + # Class-level variables + class_var = "Initial class value" + + reassignable_class_var = True + reassignable_class_var = False # Reassigned #noqa: PIE794 + + # Class-level variable with type annotation + typed_class_var: str = "typed value" + + def __init__(self): + # Instance variables + self.instance_var = "Initial instance value" + self.reassignable_instance_var = 100 + + # Instance variable with type annotation + self.typed_instance_var: list[str] = ["item1", "item2"] + + def modify_instance_var(self): + # Reassign instance variable + self.instance_var = "Modified instance value" + self.reassignable_instance_var = 200 # Reassigned + + def use_module_var(self): + # Use module-level variables + result = module_var + " used in method" + other_result = reassignable_module_var + 5 + return result, other_result + + def use_class_var(self): + # Use class-level variables + result = VariableContainer.class_var + " used in method" + other_result = VariableContainer.reassignable_class_var + return result, other_result + + + # Dataclass with variables + @dataclass + class VariableDataclass: + """Dataclass that contains various fields.""" + + # Field variables with type annotations + id: int + name: str + items: list[str] = field(default_factory=list) + metadata: dict[str, str] = field(default_factory=dict) + optional_value: float | None = None + + # This will be reassigned in various places + status: str = "pending" + + + # Function that uses the module variables + def use_module_variables(): + """Function that uses module-level variables.""" + result = module_var + " used in function" + other_result = reassignable_module_var * 2 + return result, other_result + + + # Create instances and use variables + dataclass_instance = VariableDataclass(id=1, name="Test") + dataclass_instance.status = "active" # Reassign dataclass field + + # Use variables at module level + module_result = module_var + " used at module level" + other_module_result = reassignable_module_var + 30 + + # Create a second dataclass instance with different status + second_dataclass = VariableDataclass(id=2, name="Another Test") + second_dataclass.status = "completed" # Another reassignment of status + + ''' +# --- # name: test_replace_body[test_case0] ''' """ diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index 15fb35ed..ee97149a 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -1,10 +1,15 @@ +""" +Snapshot tests using the (awesome) syrupy pytest plugin https://github.com/syrupy-project/syrupy. +Recreate the snapshots with `pytest --snapshot-update`. +""" + import logging import os import shutil import sys import tempfile import time -from abc import abstractmethod +from abc import ABC, abstractmethod from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass, field @@ -13,6 +18,8 @@ from pathlib import Path from typing import Literal, NamedTuple import pytest +from overrides import overrides +from syrupy import SnapshotAssertion from serena.code_editor import CodeEditor, LanguageServerCodeEditor from solidlsp.ls_config import Language @@ -169,7 +176,7 @@ class CodeDiff: return "".join(diff) -class EditingTest: +class EditingTest(ABC): def __init__(self, language: Language, rel_path: str): """ :param language: the language @@ -221,7 +228,7 @@ class EditingTest: with open(file_path, encoding="utf-8") as f: return f.read() - def run_test(self, content_after_ground_truth: str) -> None: + def run_test(self, content_after_ground_truth: str | SnapshotAssertion) -> None: with self._setup() as symbol_retriever: content_before = self._read_file(self.rel_path) code_editor = LanguageServerCodeEditor(symbol_retriever) @@ -234,7 +241,8 @@ class EditingTest: def _apply_edit(self, code_editor: CodeEditor) -> None: pass - def _test_diff(self, code_diff: CodeDiff, snapshot: str) -> None: + def _test_diff(self, code_diff: CodeDiff, snapshot: SnapshotAssertion) -> None: + assert code_diff.has_changes, f"Sanity check failed: No changes detected in {code_diff.relative_path}" assert code_diff.modified_content == snapshot @@ -276,7 +284,7 @@ class DeleteSymbolTest(EditingTest): ), ], ) -def test_delete_symbol(test_case, snapshot): +def test_delete_symbol(test_case, snapshot: SnapshotAssertion): test_case.run_test(content_after_ground_truth=snapshot) @@ -369,13 +377,13 @@ class InsertInRelToSymbolTest(EditingTest): ), ], ) -def test_insert_in_rel_to_symbol(test_case: InsertInRelToSymbolTest, mode: Literal["before", "after"], snapshot): +def test_insert_in_rel_to_symbol(test_case: InsertInRelToSymbolTest, mode: Literal["before", "after"], snapshot: SnapshotAssertion): test_case.set_mode(mode) test_case.run_test(content_after_ground_truth=snapshot) @pytest.mark.python -def test_insert_python_class_before(snapshot): +def test_insert_python_class_before(snapshot: SnapshotAssertion): InsertInRelToSymbolTest( Language.PYTHON, PYTHON_TEST_REL_FILE_PATH, @@ -386,7 +394,7 @@ def test_insert_python_class_before(snapshot): @pytest.mark.python -def test_insert_python_class_after(snapshot): +def test_insert_python_class_after(snapshot: SnapshotAssertion): InsertInRelToSymbolTest( Language.PYTHON, PYTHON_TEST_REL_FILE_PATH, @@ -442,7 +450,8 @@ class ReplaceBodyTest(EditingTest): ), ], ) -def test_replace_body(test_case: ReplaceBodyTest, snapshot): +def test_replace_body(test_case: ReplaceBodyTest, snapshot: SnapshotAssertion): + # assert "a" in snapshot test_case.run_test(content_after_ground_truth=snapshot) @@ -463,7 +472,7 @@ class NixAttrReplacementTest(EditingTest): @pytest.mark.nix @pytest.mark.skipif(sys.platform == "win32", reason="nixd language server doesn't run on Windows") -def test_nix_symbol_replacement_no_double_semicolon(snapshot): +def test_nix_symbol_replacement_no_double_semicolon(snapshot: SnapshotAssertion): """ Test that replacing a Nix attribute does not result in double semicolons. @@ -483,3 +492,30 @@ def test_nix_symbol_replacement_no_double_semicolon(snapshot): NIX_ATTR_REPLACEMENT, ) test_case.run_test(content_after_ground_truth=snapshot) + + +class RenameSymbolTest(EditingTest): + def __init__(self, language: Language, rel_path: str, symbol_name: str, new_name: str): + super().__init__(language, rel_path) + self.symbol_name = symbol_name + self.new_name = new_name + + def _apply_edit(self, code_editor: CodeEditor) -> None: + code_editor.rename_symbol(self.symbol_name, self.rel_path, self.new_name) + + @overrides + def _test_diff(self, code_diff: CodeDiff, snapshot: SnapshotAssertion) -> None: + # sanity check (e.g., for newly generated snapshots) that the new name is actually in the modified content + assert self.new_name in code_diff.modified_content, f"New name '{self.new_name}' not found in modified content." + return super()._test_diff(code_diff, snapshot) + + +@pytest.mark.python +def test_rename_symbol(snapshot: SnapshotAssertion): + test_case = RenameSymbolTest( + Language.PYTHON, + PYTHON_TEST_REL_FILE_PATH, + "typed_module_var", + "renamed_typed_module_var", + ) + test_case.run_test(content_after_ground_truth=snapshot) From 563e42efbdf4d6424bc4af65f926a984b32f953a Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 16 Oct 2025 13:56:51 +0200 Subject: [PATCH 006/253] Add encoding entry to project.yml template (supported since 20th June) --- src/serena/resources/project.template.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/serena/resources/project.template.yml b/src/serena/resources/project.template.yml index 59ab269c..0b2b59d9 100644 --- a/src/serena/resources/project.template.yml +++ b/src/serena/resources/project.template.yml @@ -5,6 +5,10 @@ # * csharp: Requires the presence of a .sln file in the project folder. language: python +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + # whether to use the project's gitignore file to ignore files # Added on 2025-04-07 ignore_all_files_in_gitignore: true From 4e3db0bd0938621a0b12b3c4108263f4c86c656e Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 16 Oct 2025 14:56:58 +0200 Subject: [PATCH 007/253] Consistently use the project's encoding setting, also passing it on the LS --- src/serena/agent.py | 5 +++-- src/serena/code_editor.py | 11 ++++++++++- src/serena/project.py | 2 ++ src/serena/text_utils.py | 6 ++++-- src/serena/tools/file_tools.py | 5 +++-- src/serena/tools/memory_tools.py | 2 +- src/solidlsp/ls.py | 5 +++-- src/solidlsp/ls_config.py | 2 ++ src/solidlsp/ls_utils.py | 6 +++--- 9 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index d8a491dd..1c95b5fb 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -47,6 +47,7 @@ class MemoriesManager: def __init__(self, project_root: str): self._memory_dir = Path(get_serena_managed_in_project_dir(project_root)) / "memories" self._memory_dir.mkdir(parents=True, exist_ok=True) + self._encoding = "utf-8" def _get_memory_file_path(self, name: str) -> Path: # strip all .md from the name. Models tend to get confused, sometimes passing the .md extension and sometimes not. @@ -58,12 +59,12 @@ class MemoriesManager: memory_file_path = self._get_memory_file_path(name) if not memory_file_path.exists(): return f"Memory file {name} not found, consider creating it with the `write_memory` tool if you need it." - with open(memory_file_path, encoding="utf-8") as f: + with open(memory_file_path, encoding=self._encoding) as f: return f.read() def save_memory(self, name: str, content: str) -> str: memory_file_path = self._get_memory_file_path(name) - with open(memory_file_path, "w", encoding="utf-8") as f: + with open(memory_file_path, "w", encoding=self._encoding) as f: f.write(content) return f"Memory {name} written." diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index ef00970b..5dc3fe65 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -12,6 +12,7 @@ from solidlsp.ls import LSPFileBuffer from solidlsp.ls_types import extract_text_edits from solidlsp.ls_utils import PathUtils, TextUtils +from .constants import DEFAULT_ENCODING from .project import Project from .tools.jetbrains_plugin_client import JetBrainsPluginClient @@ -28,6 +29,14 @@ class CodeEditor(Generic[TSymbol], ABC): self.project_root = project_root self.agent = agent + # set encoding based on active project, if available + encoding = DEFAULT_ENCODING + if agent is not None: + project = agent.get_active_project() + if project is not None: + encoding = agent.get_active_project().project_config.encoding + self.encoding = encoding + class EditedFile(ABC): @abstractmethod def get_contents(self) -> str: @@ -59,7 +68,7 @@ class CodeEditor(Generic[TSymbol], ABC): yield edited_file # save the file abs_path = os.path.join(self.project_root, relative_path) - with open(abs_path, "w", encoding="utf-8") as f: + with open(abs_path, "w", encoding=self.encoding) as f: f.write(edited_file.get_contents()) @abstractmethod diff --git a/src/serena/project.py b/src/serena/project.py index 966fc8c7..e1d8c743 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -249,6 +249,7 @@ class Project: relative_file_paths, pattern, root_path=self.project_root, + file_reader=self.read_file, context_lines_before=context_lines_before, context_lines_after=context_lines_after, paths_include_glob=paths_include_glob, @@ -301,6 +302,7 @@ class Project: code_language=self.language, ignored_paths=self._ignored_patterns, trace_lsp_communication=trace_lsp_communication, + encoding=self.project_config.encoding, ) ls_logger = LanguageServerLogger(log_level=log_level) diff --git a/src/serena/text_utils.py b/src/serena/text_utils.py index 19987cee..3eea8ba6 100644 --- a/src/serena/text_utils.py +++ b/src/serena/text_utils.py @@ -9,6 +9,8 @@ from typing import Any, Self from joblib import Parallel, delayed +from serena.constants import DEFAULT_ENCODING + log = logging.getLogger(__name__) @@ -236,8 +238,8 @@ def search_text( def default_file_reader(file_path: str) -> str: - """Reads using utf-8 encoding.""" - with open(file_path, encoding="utf-8") as f: + """Reads using the default encoding.""" + with open(file_path, encoding=DEFAULT_ENCODING) as f: return f.read() diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index d7ec5a41..38cfd2f7 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -59,7 +59,7 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit): Write a new file or overwrite an existing file. :param relative_path: the relative path to the file to create - :param content: the (utf-8-encoded) content to write to the file + :param content: the (appropriately encoded) content to write to the file :return: a message indicating success or failure """ project_root = self.get_project_root() @@ -74,7 +74,7 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit): ), f"Cannot create file outside of the project directory, got {relative_path=}" abs_path.parent.mkdir(parents=True, exist_ok=True) - abs_path.write_text(content, encoding="utf-8") + abs_path.write_text(content, encoding=self.project.project_config.encoding) answer = f"File created: {relative_path}." if will_overwrite_existing: answer += " Overwrote existing file." @@ -390,6 +390,7 @@ class SearchForPatternTool(Tool): matches = search_files( rel_paths_to_search, substring_pattern, + file_reader=self.project.read_file, root_path=self.get_project_root(), paths_include_glob=paths_include_glob, paths_exclude_glob=paths_exclude_glob, diff --git a/src/serena/tools/memory_tools.py b/src/serena/tools/memory_tools.py index f52ae3f4..da6d7cb3 100644 --- a/src/serena/tools/memory_tools.py +++ b/src/serena/tools/memory_tools.py @@ -10,7 +10,7 @@ class WriteMemoryTool(Tool): def apply(self, memory_name: str, content: str, max_answer_chars: int = -1) -> str: """ - Write some information about this project that can be useful for future tasks to a memory in md format. + Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format. The memory name should be meaningful. """ if max_answer_chars == -1: diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 29df1ee4..d4a9c111 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -176,6 +176,7 @@ class SolidLanguageServer(ABC): as opposed to HTTP, TCP modes supported by some language servers. """ self._solidlsp_settings = solidlsp_settings + self._encoding = config.encoding self.logger = logger self.repository_root_path: str = repository_root_path self.logger.log( @@ -391,7 +392,7 @@ class SolidLanguageServer(ABC): yield self.open_file_buffers[uri] self.open_file_buffers[uri].ref_count -= 1 else: - contents = FileUtils.read_file(self.logger, absolute_file_path) + contents = FileUtils.read_file(self.logger, absolute_file_path, self._encoding) version = 0 self.open_file_buffers[uri] = LSPFileBuffer(uri, contents, version, self.language_id, 1) @@ -1408,7 +1409,7 @@ class SolidLanguageServer(ABC): # checking if the line is empty, unfortunately ugly and duplicating code, but I don't want to refactor with self.open_file(relative_file_path): absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) - content = FileUtils.read_file(self.logger, absolute_file_path) + content = FileUtils.read_file(self.logger, absolute_file_path, self._encoding) if content.split("\n")[line].strip() == "": self.logger.log( f"Passing empty lines to request_container_symbol is currently not supported, {relative_file_path=}, {line=}", diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index d70c1dce..cc023e3b 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -306,6 +306,8 @@ class LanguageServerConfig: start_independent_lsp_process: bool = True ignored_paths: list[str] = field(default_factory=list) """Paths, dirs or glob-like patterns. The matching will follow the same logic as for .gitignore entries""" + encoding: str = "utf-8" + """File encoding to use when reading source files""" @classmethod def from_dict(cls, env: dict): diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index 11fe88f3..3cc124f9 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -165,7 +165,7 @@ class FileUtils: """ @staticmethod - def read_file(logger: LanguageServerLogger, file_path: str) -> str: + def read_file(logger: LanguageServerLogger, file_path: str, encoding: str) -> str: """ Reads the file at the given path and returns the contents as a string. """ @@ -173,10 +173,10 @@ class FileUtils: logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR) raise SolidLSPException(f"File read '{file_path}' failed: File does not exist.") try: - with open(file_path, encoding="utf-8") as inp_file: + with open(file_path, encoding=encoding) as inp_file: return inp_file.read() except Exception as exc: - logger.log(f"File read '{file_path}' failed to read with encoding 'utf-8': {exc}", logging.ERROR) + logger.log(f"File read '{file_path}' failed to read with encoding '{encoding}': {exc}", logging.ERROR) raise SolidLSPException("File read failed.") from None @staticmethod From e1ba1befa92a945e4c4496ac4a4daec40a3c722c Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Thu, 16 Oct 2025 17:56:52 +0200 Subject: [PATCH 008/253] Bugfix - special handling of edge case in ignore patterns Was falsely matching project root against something like ".*" in gitignore --- src/serena/project.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/serena/project.py b/src/serena/project.py index 966fc8c7..b2db669e 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -105,6 +105,12 @@ class Project: :return: whether the path should be ignored """ + # special case, never ignore the project root itself + # If the user ignores hidden files, "." might match against the corresponding PathSpec pattern. + # The empty string also points to the project root and should never be ignored. + if str(relative_path) in [".", ""]: + return False + abs_path = os.path.join(self.project_root, relative_path) if not os.path.exists(abs_path): raise FileNotFoundError(f"File {abs_path} not found, the ignore check cannot be performed") From fd8b0174f4c9a9c11c266bea8c6c63cde63cdaf4 Mon Sep 17 00:00:00 2001 From: Nickolay Kondratyev Date: Thu, 16 Oct 2025 12:09:40 -0600 Subject: [PATCH 009/253] [README-only-change]: Add section for git worktree usage (#680) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 07c92081..63ac9567 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ Several videos and blog posts have talked about Serena: * [Prompting Strategies](#prompting-strategies) * [Running Out of Context](#running-out-of-context) * [Serena's Logs: The Dashboard and GUI Tool](#serenas-logs-the-dashboard-and-gui-tool) + * [Serena and GIT worktrees](#serena-and-git-worktrees) - [Comparison with Other Coding Agents](#comparison-with-other-coding-agents) * [Subscription-Based Coding Agents](#subscription-based-coding-agents) * [API-Based Coding Agents](#api-based-coding-agents) @@ -723,6 +724,12 @@ In addition to viewing logs, both tools allow to shut down the Serena agent. This function is provided, because clients like Claude Desktop may fail to terminate the MCP server subprocess when they themselves are closed. +### Serena and GIT worktrees +[git-worktree](https://git-scm.com/docs/git-worktree) can be an excellent way to parallelize your work. More on this in [Anthropic: Run parallel Claude Code sessions with Git worktrees](https://docs.claude.com/en/docs/claude-code/common-workflows#run-parallel-claude-code-sessions-with-git-worktrees). + +When it comes to serena AND git-worktree AND larger projects (that take longer to index), the recommended way is to COPY your `$ORIG_PROJECT/.serena/cache` to `$GIT_WORKTREE/.serena/cache`. After you have performed pre-indexing of your project described in [Project Activation & Indexing](#project-activation--indexing) section. To avoid having to re-index per each git work tree that you create. + + ## Comparison with Other Coding Agents To our knowledge, Serena is the first fully-featured coding agent where the From b1d9ac576570740f8085b6e4891ea594ff8cb5da Mon Sep 17 00:00:00 2001 From: Nickolay Kondratyev Date: Thu, 16 Oct 2025 12:10:38 -0600 Subject: [PATCH 010/253] [README-only-change]: Add quotes around /home/nickolaykondratyev/git_repos/nickolay-kondratyev_serena per shell best practices (#679) If someone uses spaces in file names /home/nickolaykondratyev/git_repos/nickolay-kondratyev_serena is likely to not work as expected due to argument splitting (hopefully people arent using spaces, but its best practice to quote everything in shell) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63ac9567..f08cd4d0 100644 --- a/README.md +++ b/README.md @@ -376,14 +376,14 @@ Serena is a great way to make Claude Code both cheaper and more powerful! From your project directory, add serena with a command like this, ```shell -claude mcp add serena -- --context ide-assistant --project $(pwd) +claude mcp add serena -- --context ide-assistant --project "$(pwd)" ``` where `` is your way of [running the Serena MCP server](#running-the-serena-mcp-server). For example, when using `uvx`, you would run ```shell -claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context ide-assistant --project $(pwd) +claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context ide-assistant --project "$(pwd)" ``` ℹ️ Serena comes with an instruction text, and Claude needs to read it to properly use Serena's tools. From 3cde03a8fb85b47921d3de135b2c9b3e03dc29c9 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 16 Oct 2025 22:09:39 +0200 Subject: [PATCH 011/253] Introduce constant for Serena file encoding * SERENA_FILE_ENCODING for Serena-specific files (config files, memories) * DEFAULT_SOURCE_FILE_ENCODING for source files (renamed from DEFAULT_ENCODING) --- src/serena/agent.py | 3 ++- src/serena/code_editor.py | 4 ++-- src/serena/config/context_mode.py | 5 +++-- src/serena/config/serena_config.py | 13 +++++++------ src/serena/constants.py | 6 +++++- src/serena/text_utils.py | 4 ++-- src/serena/tools/memory_tools.py | 1 + src/serena/util/general.py | 6 ++++-- 8 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 1c95b5fb..9358db1d 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -22,6 +22,7 @@ from serena import serena_version from serena.analytics import RegisteredTokenCountEstimator, ToolUsageStats from serena.config.context_mode import RegisteredContext, SerenaAgentContext, SerenaAgentMode from serena.config.serena_config import SerenaConfig, ToolInclusionDefinition, ToolSet, get_serena_managed_in_project_dir +from serena.constants import SERENA_FILE_ENCODING from serena.dashboard import SerenaDashboardAPI from serena.project import Project from serena.prompt_factory import SerenaPromptFactory @@ -47,7 +48,7 @@ class MemoriesManager: def __init__(self, project_root: str): self._memory_dir = Path(get_serena_managed_in_project_dir(project_root)) / "memories" self._memory_dir.mkdir(parents=True, exist_ok=True) - self._encoding = "utf-8" + self._encoding = SERENA_FILE_ENCODING def _get_memory_file_path(self, name: str) -> Path: # strip all .md from the name. Models tend to get confused, sometimes passing the .md extension and sometimes not. diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 5dc3fe65..3d99d2bb 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -12,7 +12,7 @@ from solidlsp.ls import LSPFileBuffer from solidlsp.ls_types import extract_text_edits from solidlsp.ls_utils import PathUtils, TextUtils -from .constants import DEFAULT_ENCODING +from .constants import DEFAULT_SOURCE_FILE_ENCODING from .project import Project from .tools.jetbrains_plugin_client import JetBrainsPluginClient @@ -30,7 +30,7 @@ class CodeEditor(Generic[TSymbol], ABC): self.agent = agent # set encoding based on active project, if available - encoding = DEFAULT_ENCODING + encoding = DEFAULT_SOURCE_FILE_ENCODING if agent is not None: project = agent.get_active_project() if project is not None: diff --git a/src/serena/config/context_mode.py b/src/serena/config/context_mode.py index 44cd42fb..ed2eb11c 100644 --- a/src/serena/config/context_mode.py +++ b/src/serena/config/context_mode.py @@ -17,6 +17,7 @@ from serena.constants import ( DEFAULT_CONTEXT, DEFAULT_MODES, INTERNAL_MODE_YAMLS_DIR, + SERENA_FILE_ENCODING, SERENAS_OWN_CONTEXT_YAMLS_DIR, SERENAS_OWN_MODE_YAMLS_DIR, USER_CONTEXT_YAMLS_DIR, @@ -56,7 +57,7 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin): @classmethod def from_yaml(cls, yaml_path: str | Path) -> Self: """Load a mode from a YAML file.""" - with open(yaml_path, encoding="utf-8") as f: + with open(yaml_path, encoding=SERENA_FILE_ENCODING) as f: data = yaml.safe_load(f) name = data.pop("name", Path(yaml_path).stem) return cls(name=name, **data) @@ -139,7 +140,7 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin): @classmethod def from_yaml(cls, yaml_path: str | Path) -> Self: """Load a context from a YAML file.""" - with open(yaml_path, encoding="utf-8") as f: + with open(yaml_path, encoding=SERENA_FILE_ENCODING) as f: data = yaml.safe_load(f) name = data.pop("name", Path(yaml_path).stem) # Ensure backwards compatibility for tool_description_overrides diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 00bbc901..5b9d4462 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -19,10 +19,11 @@ from sensai.util.logging import LogTime, datetime_tag from sensai.util.string import ToStringMixin from serena.constants import ( - DEFAULT_ENCODING, + DEFAULT_SOURCE_FILE_ENCODING, PROJECT_TEMPLATE_FILE, REPO_ROOT, SERENA_CONFIG_TEMPLATE_FILE, + SERENA_FILE_ENCODING, SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME, ) @@ -166,7 +167,7 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin): read_only: bool = False ignore_all_files_in_gitignore: bool = True initial_prompt: str = "" - encoding: str = DEFAULT_ENCODING + encoding: str = DEFAULT_SOURCE_FILE_ENCODING SERENA_DEFAULT_PROJECT_FILE = "project.yml" @@ -244,7 +245,7 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin): read_only=data.get("read_only", False), ignore_all_files_in_gitignore=data.get("ignore_all_files_in_gitignore", True), initial_prompt=data.get("initial_prompt", ""), - encoding=data.get("encoding", DEFAULT_ENCODING), + encoding=data.get("encoding", DEFAULT_SOURCE_FILE_ENCODING), ) @classmethod @@ -259,7 +260,7 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin): return cls.autogenerate(project_root) else: raise FileNotFoundError(f"Project configuration file not found: {yaml_path}") - with open(yaml_path, encoding="utf-8") as f: + with open(yaml_path, encoding=SERENA_FILE_ENCODING) as f: yaml_data = yaml.safe_load(f) if "project_name" not in yaml_data: yaml_data["project_name"] = project_root.name @@ -483,11 +484,11 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): """ log.info(f"Found legacy project configuration file {path}, migrating to in-project configuration.") try: - with open(path, encoding="utf-8") as f: + with open(path, encoding=SERENA_FILE_ENCODING) as f: project_config_data = yaml.safe_load(f) if "project_name" not in project_config_data: project_name = path.stem - with open(path, "a", encoding="utf-8") as f: + with open(path, "a", encoding=SERENA_FILE_ENCODING) as f: f.write(f"\nproject_name: {project_name}") project_root = project_config_data["project_root"] shutil.move(str(path), str(Path(project_root) / ProjectConfig.rel_path_to_project_yml())) diff --git a/src/serena/constants.py b/src/serena/constants.py index 1e5d98a1..e0ed29cf 100644 --- a/src/serena/constants.py +++ b/src/serena/constants.py @@ -25,10 +25,14 @@ INTERNAL_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "inter SERENA_DASHBOARD_DIR = str(_serena_pkg_path / "resources" / "dashboard") SERENA_ICON_DIR = str(_serena_pkg_path / "resources" / "icons") -DEFAULT_ENCODING = "utf-8" +DEFAULT_SOURCE_FILE_ENCODING = "utf-8" +"""The default encoding assumed for project source files.""" DEFAULT_CONTEXT = "desktop-app" DEFAULT_MODES = ("interactive", "editing") +SERENA_FILE_ENCODING = "utf-8" +"""The encoding used for Serena's own files, such as configuration files and memories.""" + PROJECT_TEMPLATE_FILE = str(_serena_pkg_path / "resources" / "project.template.yml") SERENA_CONFIG_TEMPLATE_FILE = str(_serena_pkg_path / "resources" / "serena_config.template.yml") diff --git a/src/serena/text_utils.py b/src/serena/text_utils.py index 3eea8ba6..d87e9ad6 100644 --- a/src/serena/text_utils.py +++ b/src/serena/text_utils.py @@ -9,7 +9,7 @@ from typing import Any, Self from joblib import Parallel, delayed -from serena.constants import DEFAULT_ENCODING +from serena.constants import DEFAULT_SOURCE_FILE_ENCODING log = logging.getLogger(__name__) @@ -239,7 +239,7 @@ def search_text( def default_file_reader(file_path: str) -> str: """Reads using the default encoding.""" - with open(file_path, encoding=DEFAULT_ENCODING) as f: + with open(file_path, encoding=DEFAULT_SOURCE_FILE_ENCODING) as f: return f.read() diff --git a/src/serena/tools/memory_tools.py b/src/serena/tools/memory_tools.py index da6d7cb3..900a04a0 100644 --- a/src/serena/tools/memory_tools.py +++ b/src/serena/tools/memory_tools.py @@ -13,6 +13,7 @@ class WriteMemoryTool(Tool): Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format. The memory name should be meaningful. """ + # NOTE: utf-8 encoding is configured in the MemoriesManager if max_answer_chars == -1: max_answer_chars = self.agent.serena_config.default_max_tool_answer_chars if len(content) > max_answer_chars: diff --git a/src/serena/util/general.py b/src/serena/util/general.py index b350ae18..4a1397e2 100644 --- a/src/serena/util/general.py +++ b/src/serena/util/general.py @@ -4,6 +4,8 @@ from typing import Literal, overload from ruamel.yaml import YAML from ruamel.yaml.comments import CommentedMap +from serena.constants import SERENA_FILE_ENCODING + def _create_YAML(preserve_comments: bool = False) -> YAML: """ @@ -20,7 +22,7 @@ def load_yaml(path: str, preserve_comments: Literal[False]) -> dict: ... @overload def load_yaml(path: str, preserve_comments: Literal[True]) -> CommentedMap: ... def load_yaml(path: str, preserve_comments: bool = False) -> dict | CommentedMap: - with open(path, encoding="utf-8") as f: + with open(path, encoding=SERENA_FILE_ENCODING) as f: yaml = _create_YAML(preserve_comments) return yaml.load(f) @@ -28,5 +30,5 @@ def load_yaml(path: str, preserve_comments: bool = False) -> dict | CommentedMap def save_yaml(path: str, data: dict | CommentedMap, preserve_comments: bool = False) -> None: yaml = _create_YAML(preserve_comments) os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, "w", encoding="utf-8") as f: + with open(path, "w", encoding=SERENA_FILE_ENCODING) as f: yaml.dump(data, f) From 5d7aeb680f6c814a697a981d1b442e3970b55278 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 16 Oct 2025 22:35:31 +0200 Subject: [PATCH 012/253] Intelephense: Account for spaces in launch command (use robust list syntax instead of string) --- src/solidlsp/language_servers/intelephense.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py index b9e86619..3f4f6e0b 100644 --- a/src/solidlsp/language_servers/intelephense.py +++ b/src/solidlsp/language_servers/intelephense.py @@ -39,9 +39,7 @@ class Intelephense(SolidLanguageServer): return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"] @classmethod - def _setup_runtime_dependencies( - cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings - ) -> str: + def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> list[str]: """ Setup runtime dependencies for Intelephense and return the command to start the server. """ @@ -84,13 +82,13 @@ class Intelephense(SolidLanguageServer): intelephense_executable_path ), f"intelephense executable not found at {intelephense_executable_path}, something went wrong." - return f"{intelephense_executable_path} --stdio" + return [intelephense_executable_path, "--stdio"] def __init__( self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings ): # Setup runtime dependencies before initializing - intelephense_cmd = self._setup_runtime_dependencies(logger, config, solidlsp_settings) + intelephense_cmd = self._setup_runtime_dependencies(logger, solidlsp_settings) super().__init__( config, From 89340ef19733ab243883d56c275524dc881498d8 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 17 Oct 2025 13:40:57 +0200 Subject: [PATCH 013/253] Fix project-specific information (prompt, encoding, etc.) not being provided when the project is activated at startup (ActivateProjectTool not applied) Peripheral change: Move MemoriesManager into module 'project', moving the instance into the Project instance, because memories are strictly associated with the project Fixes #681 --- src/serena/agent.py | 47 ++++-------------------- src/serena/project.py | 61 ++++++++++++++++++++++++++++++-- src/serena/tools/config_tools.py | 24 ++----------- src/serena/tools/tools_base.py | 7 ++-- 4 files changed, 70 insertions(+), 69 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 9358db1d..3c15b8fb 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -11,7 +11,6 @@ import webbrowser from collections.abc import Callable from concurrent.futures import Future, ThreadPoolExecutor from logging import Logger -from pathlib import Path from typing import TYPE_CHECKING, Any, Optional, TypeVar from sensai.util import logging @@ -21,8 +20,7 @@ from interprompt.jinja_template import JinjaTemplate from serena import serena_version from serena.analytics import RegisteredTokenCountEstimator, ToolUsageStats from serena.config.context_mode import RegisteredContext, SerenaAgentContext, SerenaAgentMode -from serena.config.serena_config import SerenaConfig, ToolInclusionDefinition, ToolSet, get_serena_managed_in_project_dir -from serena.constants import SERENA_FILE_ENCODING +from serena.config.serena_config import SerenaConfig, ToolInclusionDefinition, ToolSet from serena.dashboard import SerenaDashboardAPI from serena.project import Project from serena.prompt_factory import SerenaPromptFactory @@ -44,40 +42,6 @@ class ProjectNotFoundError(Exception): pass -class MemoriesManager: - def __init__(self, project_root: str): - self._memory_dir = Path(get_serena_managed_in_project_dir(project_root)) / "memories" - self._memory_dir.mkdir(parents=True, exist_ok=True) - self._encoding = SERENA_FILE_ENCODING - - def _get_memory_file_path(self, name: str) -> Path: - # strip all .md from the name. Models tend to get confused, sometimes passing the .md extension and sometimes not. - name = name.replace(".md", "") - filename = f"{name}.md" - return self._memory_dir / filename - - def load_memory(self, name: str) -> str: - memory_file_path = self._get_memory_file_path(name) - if not memory_file_path.exists(): - return f"Memory file {name} not found, consider creating it with the `write_memory` tool if you need it." - with open(memory_file_path, encoding=self._encoding) as f: - return f.read() - - def save_memory(self, name: str, content: str) -> str: - memory_file_path = self._get_memory_file_path(name) - with open(memory_file_path, "w", encoding=self._encoding) as f: - f.write(content) - return f"Memory {name} written." - - def list_memories(self) -> list[str]: - return [f.name.replace(".md", "") for f in self._memory_dir.iterdir() if f.is_file()] - - def delete_memory(self, name: str) -> str: - memory_file_path = self._get_memory_file_path(name) - memory_file_path.unlink() - return f"Memory {name} deleted." - - class AvailableTools: def __init__(self, tools: list[Tool]): """ @@ -123,7 +87,6 @@ class SerenaAgent: # project-specific instances, which will be initialized upon project activation self._active_project: Project | None = None self.language_server: SolidLanguageServer | None = None - self.memories_manager: MemoriesManager | None = None # adjust log level serena_log_level = self.serena_config.log_level @@ -361,6 +324,11 @@ class SerenaAgent: available_tools=self._exposed_tools.tool_names, available_markers=available_markers, ) + + # If a project is active at startup, append its activation message + if self._active_project is not None: + system_prompt += "\n\n" + self._active_project.get_activation_message() + log.info("System prompt:\n%s", system_prompt) return system_prompt @@ -426,9 +394,6 @@ class SerenaAgent: self._active_project = project self._update_active_tools() - # initialize project-specific instances which do not depend on the language server - self.memories_manager = MemoriesManager(project.project_root) - def init_language_server() -> None: # start the language server with LogTime("Language server initialization", logger=log): diff --git a/src/serena/project.py b/src/serena/project.py index b326f5db..4a0fae8c 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -1,3 +1,4 @@ +import json import logging import os from pathlib import Path @@ -5,8 +6,8 @@ from typing import Any import pathspec -from serena.config.serena_config import DEFAULT_TOOL_TIMEOUT, ProjectConfig -from serena.constants import SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME +from serena.config.serena_config import DEFAULT_TOOL_TIMEOUT, ProjectConfig, get_serena_managed_in_project_dir +from serena.constants import SERENA_FILE_ENCODING, SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME from serena.text_utils import MatchedConsecutiveLines, search_files from serena.util.file_system import GitignoreParser, match_path from solidlsp import SolidLanguageServer @@ -17,11 +18,46 @@ from solidlsp.settings import SolidLSPSettings log = logging.getLogger(__name__) +class MemoriesManager: + def __init__(self, project_root: str): + self._memory_dir = Path(get_serena_managed_in_project_dir(project_root)) / "memories" + self._memory_dir.mkdir(parents=True, exist_ok=True) + self._encoding = SERENA_FILE_ENCODING + + def _get_memory_file_path(self, name: str) -> Path: + # strip all .md from the name. Models tend to get confused, sometimes passing the .md extension and sometimes not. + name = name.replace(".md", "") + filename = f"{name}.md" + return self._memory_dir / filename + + def load_memory(self, name: str) -> str: + memory_file_path = self._get_memory_file_path(name) + if not memory_file_path.exists(): + return f"Memory file {name} not found, consider creating it with the `write_memory` tool if you need it." + with open(memory_file_path, encoding=self._encoding) as f: + return f.read() + + def save_memory(self, name: str, content: str) -> str: + memory_file_path = self._get_memory_file_path(name) + with open(memory_file_path, "w", encoding=self._encoding) as f: + f.write(content) + return f"Memory {name} written." + + def list_memories(self) -> list[str]: + return [f.name.replace(".md", "") for f in self._memory_dir.iterdir() if f.is_file()] + + def delete_memory(self, name: str) -> str: + memory_file_path = self._get_memory_file_path(name) + memory_file_path.unlink() + return f"Memory {name} deleted." + + class Project: def __init__(self, project_root: str, project_config: ProjectConfig, is_newly_created: bool = False): self.project_root = project_root self.project_config = project_config - self.is_newly_created = is_newly_created + self.memories_manager = MemoriesManager(project_root) + self._is_newly_created = is_newly_created # create .gitignore file in the project's Serena data folder if not yet present serena_data_gitignore_path = os.path.join(self.path_to_serena_data_folder(), ".gitignore") @@ -75,6 +111,25 @@ class Project: def path_to_project_yml(self) -> str: return os.path.join(self.project_root, self.project_config.rel_path_to_project_yml()) + def get_activation_message(self) -> str: + """ + :return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt) + """ + if self._is_newly_created: + msg = f"Created and activated a new project with name '{self.project_name}' at {self.project_root}. " + else: + msg = f"The project with name '{self.project_name}' at {self.project_root} is activated." + msg += f"\nProgramming language: {self.project_config.language.value}; file encoding: {self.project_config.encoding}" + memories = self.memories_manager.list_memories() + if memories: + msg += ( + f"\nAvailable project memories: {json.dumps(memories)}\n" + + "Use the `read_memory` tool to read these memories later if they are relevant to the task." + ) + if self.project_config.initial_prompt: + msg += f"\nAdditional project-specific instructions:\n {self.project_config.initial_prompt}" + return msg + def read_file(self, relative_path: str) -> str: """ Reads a file relative to the project root. diff --git a/src/serena/tools/config_tools.py b/src/serena/tools/config_tools.py index 2110e33f..bc856ec5 100644 --- a/src/serena/tools/config_tools.py +++ b/src/serena/tools/config_tools.py @@ -1,38 +1,20 @@ -import json - from serena.config.context_mode import SerenaAgentMode from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject): """ - Activates a project by name. + Activates a project based on the project name or path. """ def apply(self, project: str) -> str: """ - Activates the project with the given name. + Activates the project with the given name or path. :param project: the name of a registered project to activate or a path to a project directory """ active_project = self.agent.activate_project_from_path_or_name(project) - if active_project.is_newly_created: - result_str = ( - f"Created and activated a new project with name '{active_project.project_name}' at {active_project.project_root}, language: {active_project.project_config.language.value}. " - "You can activate this project later by name.\n" - f"The project's Serena configuration is in {active_project.path_to_project_yml()}. In particular, you may want to edit the project name and the initial prompt." - ) - else: - result_str = f"Activated existing project with name '{active_project.project_name}' at {active_project.project_root}, language: {active_project.project_config.language.value}" - - if active_project.project_config.initial_prompt: - result_str += f"\nAdditional project information:\n {active_project.project_config.initial_prompt}" - result_str += ( - f"\nAvailable memories:\n {json.dumps(list(self.memories_manager.list_memories()))}" - + "You should not read these memories directly, but rather use the `read_memory` tool to read them later if needed for the task." - ) - result_str += f"\nAvailable tools:\n {json.dumps(self.agent.get_active_tool_names())}" - return result_str + return active_project.get_activation_message() class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional): diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 3f2d2c25..74c53322 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -10,7 +10,7 @@ from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata, func_metada from sensai.util import logging from sensai.util.string import dict_string -from serena.project import Project +from serena.project import MemoriesManager, Project from serena.prompt_factory import PromptFactory from serena.symbol import LanguageServerSymbolRetriever from serena.util.class_decorators import singleton @@ -18,7 +18,7 @@ from serena.util.inspection import iter_subclasses from solidlsp.ls_exceptions import SolidLSPException if TYPE_CHECKING: - from serena.agent import MemoriesManager, SerenaAgent + from serena.agent import SerenaAgent from serena.code_editor import CodeEditor log = logging.getLogger(__name__) @@ -42,8 +42,7 @@ class Component(ABC): @property def memories_manager(self) -> "MemoriesManager": - assert self.agent.memories_manager is not None - return self.agent.memories_manager + return self.project.memories_manager def create_language_server_symbol_retriever(self) -> LanguageServerSymbolRetriever: if not self.agent.is_using_language_server(): From 4d7072d9ed7679a14ead7af690ba3ac090f743c6 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 17 Oct 2025 14:32:08 +0200 Subject: [PATCH 014/253] Fix mypy issues --- src/serena/code_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 3d99d2bb..5aaba834 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -34,7 +34,7 @@ class CodeEditor(Generic[TSymbol], ABC): if agent is not None: project = agent.get_active_project() if project is not None: - encoding = agent.get_active_project().project_config.encoding + encoding = project.project_config.encoding self.encoding = encoding class EditedFile(ABC): @@ -292,7 +292,7 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): relative_path = os.path.relpath(file_path, self._lang_server.repository_root_path) modified_relative_paths.append(relative_path) with self._edited_file_context(relative_path) as edited_file: - edited_file = cast(self.EditedFile, edited_file) + edited_file = cast(LanguageServerCodeEditor.EditedFile, edited_file) edited_file.apply_text_edits(edits) return modified_relative_paths From 19b56d94bb2982e6c2b3f4c2834e79d42dcf81cb Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 17 Oct 2025 18:10:05 +0200 Subject: [PATCH 015/253] Support rename_symbol for JetBrains --- src/serena/code_editor.py | 33 ++++++++++----------- src/serena/tools/jetbrains_plugin_client.py | 25 ++++++++++++++-- src/serena/tools/symbol_tools.py | 4 +-- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 5aaba834..6fb1e668 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -218,14 +218,14 @@ class CodeEditor(Generic[TSymbol], ABC): edited_file.delete_text_between_positions(start_pos, end_pos) @abstractmethod - def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> list[str]: + def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str: """ Renames the symbol with the given name throughout the codebase. :param name_path: the name path of the symbol to rename :param relative_file_path: the relative path of the file containing the symbol :param new_name: the new name for the symbol - :return: list of files that were modified + :return: a status message """ @@ -296,14 +296,7 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): edited_file.apply_text_edits(edits) return modified_relative_paths - def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> list[str]: - """ - Renames the symbol with the given name throughout the codebase. - :param name_path: - :param relative_file_path: - :param new_name: - :return: list of files that were modified - """ + def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str: symbol = self._find_unique_symbol(name_path, relative_file_path) if not symbol.location.has_position_in_file(): raise ValueError(f"Symbol '{name_path}' does not have a valid position in file for renaming") @@ -320,7 +313,9 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): f"Language server for {self._lang_server.language_id} returned no rename edits for symbol '{name_path}'. " f"The symbol might not support renaming." ) - return self._apply_workspace_edit(rename_result) + modified_files = self._apply_workspace_edit(rename_result) + msg = f"Successfully renamed '{name_path}' to '{new_name}' in {len(modified_files)} file(s)" + return msg class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): @@ -363,9 +358,13 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): ) return JetBrainsSymbol(symbols[0], self._project) - def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> list[str]: - """ - Renames the symbol with the given name throughout the codebase. - Not yet implemented for JetBrains code editor. - """ - raise NotImplementedError("Symbol renaming is not yet supported for JetBrains code editor") + def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str: + with JetBrainsPluginClient.from_project(self._project) as client: + client.rename_symbol( + name_path=name_path, + relative_path=relative_file_path, + new_name=new_name, + rename_in_comments=False, + rename_in_text_occurrences=False, + ) + return "Success" diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index 5a845a6e..47efb30a 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -136,7 +136,7 @@ class JetBrainsPluginClient(ToStringMixin): self, name_path: str, relative_path: str | None = None, include_body: bool = False, depth: int = 0, include_location: bool = False ) -> dict[str, Any]: """ - Find symbols by name. + Finds symbols by name. :param name_path: the name path to match :param relative_path: the relative path to which to restrict the search @@ -156,7 +156,7 @@ class JetBrainsPluginClient(ToStringMixin): def find_references(self, name_path: str, relative_path: str) -> dict[str, Any]: """ - Find references to a symbol. + Finds references to a symbol. :param name_path: the name path of the symbol :param relative_path: the relative path @@ -172,6 +172,27 @@ class JetBrainsPluginClient(ToStringMixin): request_data = {"relativePath": relative_path} return self._make_request("POST", "/getSymbolsOverview", request_data) + def rename_symbol( + self, name_path: str, relative_path: str, new_name: str, rename_in_comments: bool, rename_in_text_occurrences: bool + ) -> None: + """ + Renames a symbol. + + :param name_path: the name path of the symbol + :param relative_path: the relative path + :param new_name: the new name for the symbol + :param rename_in_comments: whether to rename in comments + :param rename_in_text_occurrences: whether to rename in text occurrences + """ + request_data = { + "namePath": name_path, + "relativePath": relative_path, + "newName": new_name, + "renameInComments": rename_in_comments, + "renameInTextOccurrences": rename_in_text_occurrences, + } + self._make_request("POST", "/renameSymbol", request_data) + def is_service_available(self) -> bool: try: self.project_root() diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 7526d20e..1f8cbf58 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -319,5 +319,5 @@ class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit): :return: result summary indicating success or failure """ code_editor = self.create_code_editor() - modified_files = code_editor.rename_symbol(name_path, relative_file_path=relative_path, new_name=new_name) - return f"Successfully renamed '{name_path}' to '{new_name}' in {len(modified_files)} file(s)" + status_message = code_editor.rename_symbol(name_path, relative_file_path=relative_path, new_name=new_name) + return status_message From 6a28fff9d058624e014f2153444e59bdc545954a Mon Sep 17 00:00:00 2001 From: youM Date: Sun, 19 Oct 2025 19:47:52 +0900 Subject: [PATCH 016/253] Feature: Support Scala Language (#528) Unlike other language servers, metals LS only works properly when the project has been compiled in a particular way. Compiling like this on the fly or in CI is unfeasible, so instead, we expect the user to setup and compile the project correctly when using Serena for scala. Extensive documentation has been added. Scala tests exist, but we don't run them in CI. They have been executed manually on macOS and Windows, thereby verifying that after following the instructions, Scala support works in Serena. --- .gitignore | 8 + docs/scala_setup_guide_for_serena.md | 67 +++++++ pyproject.toml | 2 + .../language_servers/scala_language_server.py | 174 ++++++++++++++++++ src/solidlsp/ls_config.py | 7 + test/resources/repos/scala/build.sbt | 1 + .../repos/scala/project/build.properties | 1 + test/resources/repos/scala/project/metals.sbt | 8 + .../resources/repos/scala/project/plugins.sbt | 1 + .../src/main/scala/com/example/Main.scala | 20 ++ .../src/main/scala/com/example/Utils.scala | 11 ++ .../erlang/test_erlang_ignored_dirs.py | 2 +- .../erlang/test_erlang_symbol_retrieval.py | 4 +- .../scala/test_scala_language_server.py | 62 +++++++ 14 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 docs/scala_setup_guide_for_serena.md create mode 100644 src/solidlsp/language_servers/scala_language_server.py create mode 100644 test/resources/repos/scala/build.sbt create mode 100644 test/resources/repos/scala/project/build.properties create mode 100644 test/resources/repos/scala/project/metals.sbt create mode 100644 test/resources/repos/scala/project/plugins.sbt create mode 100644 test/resources/repos/scala/src/main/scala/com/example/Main.scala create mode 100644 test/resources/repos/scala/src/main/scala/com/example/Utils.scala create mode 100644 test/solidlsp/scala/test_scala_language_server.py diff --git a/.gitignore b/.gitignore index 196476e6..3809efdb 100644 --- a/.gitignore +++ b/.gitignore @@ -238,3 +238,11 @@ tmp/ # Elm /test/resources/repos/elm/test_repo/.elm/ /test/resources/repos/elm/test_repo/elm-stuff/ + +# Scala +.metals/ +.bsp/ +.scala-build/ +.bloop/ +bootstrap +test/resources/repos/scala/.bloop/ \ No newline at end of file diff --git a/docs/scala_setup_guide_for_serena.md b/docs/scala_setup_guide_for_serena.md new file mode 100644 index 00000000..2c4b1cc8 --- /dev/null +++ b/docs/scala_setup_guide_for_serena.md @@ -0,0 +1,67 @@ +# Scala Setup Guide for Serena + +This guide explains how to prepare a Scala project so that Serena can provide reliable code intelligence via Metals (Scala LSP) and how to run Scala tests manually. + +Serena automatically bootstraps the Metals language server using Coursier when needed. Your project, however, must be importable by a build server (BSP) — typically via Bloop or sbt’s built‑in BSP — so that Metals can compile and index your code. + +--- +## Prerequisites + +Install the following on your system and ensure they are available on `PATH`: + +- Java Development Kit (JDK). A modern LTS (e.g., 17 or 21) is recommended. +- `sbt` +- Coursier command (`cs`) or the legacy `coursier` launcher + - Serena uses `cs` if available; if only `coursier` exists, it will attempt to install `cs`. If neither is present, install Coursier first. + +--- +## Quick Start (Recommended: VS Code + Metals auto‑import) + +1. Open your Scala project in VS Code. +2. When prompted by Metals, accept “Import build”. Wait until the import and initial compile/indexing finish. +3. Run the “Connect to build server” command (id: `build.connect`). +4. Once the import completes, start Serena in your project root and use it. + +This flow ensures the `.bloop/` and (if applicable) `.metals/` directories are created and your build is known to the build server that Metals uses. + +--- +## Manual Setup (No VS Code) + +Follow these steps if you prefer a manual setup or you are not using VS Code: + +These instructions cover the setup for projects that use sbt as the build tool, with Bloop as the BSP server. + + +1. Add Bloop to `project/plugins.sbt` in your Scala project: + ```scala + // project/plugins.sbt + addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "") + ``` + Replace `` with an appropriate current version from the Metals documentation. + +2. Export Bloop configuration with sources: + ```bash + sbt -Dbloop.export-jar-classifiers=sources bloopInstall + ``` + This creates a `.bloop/` directory containing your project’s build metadata for the BSP server. + +3. Compile from sbt to verify the build: + ```bash + sbt compile + ``` + +4. Start Serena in your project root. Serena will bootstrap Metals (if not already present) and connect to the build server using the configuration exported above. + +--- +## Using Serena with Scala + +- Serena automatically detects Scala files (`*.scala`, `*.sbt`) and will start a Metals process per project when needed. +- On first run, you may see messages like “Bootstrapping metals…” in the Serena logs — this is expected. +- Optimal results require that your project compiles successfully via the build server (BSP). If compilation fails, fix build errors in `sbt` first. + + +Notes: +- Ensure you completed the manual or auto‑import steps so that the build is compiled and indexed; otherwise, code navigation and references may be incomplete until the first successful compile. + +## Reference +- Metals + sbt: [https://scalameta.org/metals/docs/build-tools/sbt](https://scalameta.org/metals/docs/build-tools/sbt) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index fb7c230d..b8e62d81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -275,6 +275,8 @@ markers = [ "nix: language server running for Nix", "dart: language server running for Dart", "erlang: language server running for Erlang", + "scala: language server running for Scala", + "scala: language server running for Scala", "al: language server running for AL (Microsoft Dynamics 365 Business Central)", "rego: language server running for Rego", "markdown: language server running for Markdown", diff --git a/src/solidlsp/language_servers/scala_language_server.py b/src/solidlsp/language_servers/scala_language_server.py new file mode 100644 index 00000000..a19252fc --- /dev/null +++ b/src/solidlsp/language_servers/scala_language_server.py @@ -0,0 +1,174 @@ +""" +Provides Scala specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Scala. +""" + +import logging +import os +import pathlib +import shutil +import subprocess + +from overrides import override + +from solidlsp.ls import SolidLanguageServer +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import PlatformUtils +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.settings import SolidLSPSettings + +if not PlatformUtils.get_platform_id().value.startswith("win"): + pass + + +class ScalaLanguageServer(SolidLanguageServer): + """ + Provides Scala specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Scala. + """ + + def __init__( + self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings + ): + """ + Creates a ScalaLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. + """ + scala_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings) + super().__init__( + config, + logger, + repository_root_path, + ProcessLaunchInfo(cmd=scala_lsp_executable_path, cwd=repository_root_path), + config.code_language.value, + solidlsp_settings, + ) + + @override + def is_ignored_dirname(self, dirname: str) -> bool: + return super().is_ignored_dirname(dirname) or dirname in [ + ".bloop", + ".metals", + "target", + ] + + @classmethod + def _setup_runtime_dependencies( + cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings + ) -> list[str]: + """ + Setup runtime dependencies for Scala Language Server and return the command to start the server. + """ + assert shutil.which("java") is not None, "JDK is not installed or not in PATH." + + metals_home = os.path.join(cls.ls_resources_dir(solidlsp_settings), "metals-lsp") + os.makedirs(metals_home, exist_ok=True) + metals_executable = os.path.join(metals_home, "metals") + coursier_command_path = shutil.which("coursier") + cs_command_path = shutil.which("cs") + + if not os.path.exists(metals_executable): + if not cs_command_path: + logger.log("'cs' command not found. Trying to install it using 'coursier'.", logging.INFO) + try: + logger.log("Running 'coursier setup --yes' to install 'cs'...", logging.INFO) + subprocess.run([coursier_command_path, "setup", "--yes"], check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as e: + raise RuntimeError(f"Failed to set up 'cs' command with 'coursier setup'. Stderr: {e.stderr}") + + cs_command_path = shutil.which("cs") + if not cs_command_path: + raise RuntimeError( + "'cs' command not found after running 'coursier setup'. Please check your PATH or install it manually." + ) + logger.log("'cs' command installed successfully.", logging.INFO) + + logger.log(f"metals executable not found at {metals_executable}, bootstrapping...", logging.INFO) + artifact = "org.scalameta:metals_2.13:1.6.2" + cmd = [ + cs_command_path, + "bootstrap", + "--java-opt", + "-XX:+UseG1GC", + "--java-opt", + "-XX:+UseStringDeduplication", + "--java-opt", + "-Xss4m", + "--java-opt", + "-Xms100m", + "--java-opt", + "-Dmetals.client=Serena", + artifact, + "-o", + metals_executable, + "-f", + ] + logger.log("Bootstrapping metals...", logging.INFO) + subprocess.run(cmd, cwd=metals_home, check=True) + logger.log("Bootstrapping metals finished.", logging.INFO) + return [metals_executable] + + @staticmethod + def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: + """ + Returns the initialize params for the Scala Language Server. + """ + root_uri = pathlib.Path(repository_absolute_path).as_uri() + initialize_params = { + "locale": "en", + "processId": os.getpid(), + "rootPath": repository_absolute_path, + "rootUri": root_uri, + "initializationOptions": { + "compilerOptions": { + "completionCommand": None, + "isCompletionItemDetailEnabled": True, + "isCompletionItemDocumentationEnabled": True, + "isCompletionItemResolve": True, + "isHoverDocumentationEnabled": True, + "isSignatureHelpDocumentationEnabled": True, + "overrideDefFormat": "ascli", + "snippetAutoIndent": False, + }, + "debuggingProvider": True, + "decorationProvider": False, + "didFocusProvider": False, + "doctorProvider": False, + "executeClientCommandProvider": False, + "globSyntax": "uri", + "icons": "unicode", + "inputBoxProvider": False, + "isVirtualDocumentSupported": False, + "isExitOnShutdown": True, + "isHttpEnabled": True, + "openFilesOnRenameProvider": False, + "quickPickProvider": False, + "renameFileThreshold": 200, + "statusBarProvider": "false", + "treeViewProvider": False, + "testExplorerProvider": False, + "openNewWindowProvider": False, + "copyWorksheetOutputProvider": False, + "doctorVisibilityProvider": False, + }, + } + return initialize_params + + def _start_server(self): + """ + Starts the Scala Language Server + """ + self.logger.log("Starting Scala server process", logging.INFO) + self.server.start() + + self.logger.log( + "Sending initialize request from LSP client to LSP server and awaiting response", + logging.INFO, + ) + + initialize_params = self._get_initialize_params(self.repository_root_path) + self.server.send.initialize(initialize_params) + self.server.notify.initialized({}) + + @override + def _get_wait_time_for_cross_file_referencing(self) -> float: + return 5 diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index cc023e3b..ca76ae72 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -56,6 +56,7 @@ class Language(str, Enum): ERLANG = "erlang" AL = "al" REGO = "rego" + SCALA = "scala" # Experimental or deprecated Language Servers TYPESCRIPT_VTS = "typescript_vts" """Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls""" @@ -152,6 +153,8 @@ class Language(str, Enum): return FilenameMatcher("*.rego") case self.MARKDOWN: return FilenameMatcher("*.md", "*.markdown") + case self.SCALA: + return FilenameMatcher("*.scala", "*.sbt") case _: raise ValueError(f"Unhandled language: {self}") @@ -277,6 +280,10 @@ class Language(str, Enum): from solidlsp.language_servers.r_language_server import RLanguageServer return RLanguageServer + case self.SCALA: + from solidlsp.language_servers.scala_language_server import ScalaLanguageServer + + return ScalaLanguageServer case _: raise ValueError(f"Unhandled language: {self}") diff --git a/test/resources/repos/scala/build.sbt b/test/resources/repos/scala/build.sbt new file mode 100644 index 00000000..d00e145b --- /dev/null +++ b/test/resources/repos/scala/build.sbt @@ -0,0 +1 @@ +scalaVersion := "2.13.16" diff --git a/test/resources/repos/scala/project/build.properties b/test/resources/repos/scala/project/build.properties new file mode 100644 index 00000000..ee4c672c --- /dev/null +++ b/test/resources/repos/scala/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.10.1 diff --git a/test/resources/repos/scala/project/metals.sbt b/test/resources/repos/scala/project/metals.sbt new file mode 100644 index 00000000..69465cb5 --- /dev/null +++ b/test/resources/repos/scala/project/metals.sbt @@ -0,0 +1,8 @@ +// format: off +// DO NOT EDIT! This file is auto-generated. + +// This file enables sbt-bloop to create bloop config files. + +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.13") + +// format: on diff --git a/test/resources/repos/scala/project/plugins.sbt b/test/resources/repos/scala/project/plugins.sbt new file mode 100644 index 00000000..a739802c --- /dev/null +++ b/test/resources/repos/scala/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "v2.0.14") \ No newline at end of file diff --git a/test/resources/repos/scala/src/main/scala/com/example/Main.scala b/test/resources/repos/scala/src/main/scala/com/example/Main.scala new file mode 100644 index 00000000..f7197ec3 --- /dev/null +++ b/test/resources/repos/scala/src/main/scala/com/example/Main.scala @@ -0,0 +1,20 @@ +package com.example + +object Main { + def main(args: Array[String]): Unit = { + println("Hello, Scala!") + + // Use Utils from another file + Utils.printHello() + val result = Utils.multiply(3, 4) + println(s"3 * 4 = $result") + + // Call local methods + val sum = add(5, 3) + println(s"5 + 3 = $sum") + } + + def add(a: Int, b: Int): Int = { + a + b + } +} diff --git a/test/resources/repos/scala/src/main/scala/com/example/Utils.scala b/test/resources/repos/scala/src/main/scala/com/example/Utils.scala new file mode 100644 index 00000000..0b6edf4b --- /dev/null +++ b/test/resources/repos/scala/src/main/scala/com/example/Utils.scala @@ -0,0 +1,11 @@ +package com.example + +object Utils { + def printHello(): Unit = { + println("Hello from Utils!") + } + + def multiply(x: Int, y: Int): Int = { + x * y + } +} diff --git a/test/solidlsp/erlang/test_erlang_ignored_dirs.py b/test/solidlsp/erlang/test_erlang_ignored_dirs.py index f36c1df5..bbb198a4 100644 --- a/test/solidlsp/erlang/test_erlang_ignored_dirs.py +++ b/test/solidlsp/erlang/test_erlang_ignored_dirs.py @@ -81,7 +81,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer): assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored" -@pytest.mark.timeout(90) # Longer timeout for this complex test +@pytest.mark.timeout(60) # Add 60 second timeout @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False) @pytest.mark.parametrize("repo_path", [Language.ERLANG], indirect=True) def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None: diff --git a/test/solidlsp/erlang/test_erlang_symbol_retrieval.py b/test/solidlsp/erlang/test_erlang_symbol_retrieval.py index b113030c..f980853b 100644 --- a/test/solidlsp/erlang/test_erlang_symbol_retrieval.py +++ b/test/solidlsp/erlang/test_erlang_symbol_retrieval.py @@ -306,7 +306,7 @@ class TestErlangLanguageServerSymbols: # We should find some references or none (both are valid outcomes) assert isinstance(refs, list) - @pytest.mark.timeout(120) # Add explicit timeout for this complex test + @pytest.mark.timeout(60) # Add 60 second timeout @pytest.mark.xfail( reason="Known intermittent timeout issue in Erlang LS in CI environments. " "May pass locally but can timeout on slower CI systems.", @@ -405,7 +405,7 @@ class TestErlangLanguageServerSymbols: expected_names = ["models", "create_user"] assert any(name in containing_symbol["name"] for name in expected_names) - @pytest.mark.timeout(90) # Add explicit timeout + @pytest.mark.timeout(60) # Add 60 second timeout @pytest.mark.xfail( reason="Known intermittent timeout issue in Erlang LS in CI environments. " "May pass locally but can timeout on slower CI systems, especially macOS. " diff --git a/test/solidlsp/scala/test_scala_language_server.py b/test/solidlsp/scala/test_scala_language_server.py new file mode 100644 index 00000000..2b7f4bea --- /dev/null +++ b/test/solidlsp/scala/test_scala_language_server.py @@ -0,0 +1,62 @@ +import logging +import os + +import pytest + +from solidlsp.language_servers.scala_language_server import ScalaLanguageServer +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.settings import SolidLSPSettings + +pytest.skip("Scala must be compiled for these tests to run through, which is a huge hassle", allow_module_level=True) + +MAIN_FILE_PATH = os.path.join("src", "main", "scala", "com", "example", "Main.scala") + +pytestmark = pytest.mark.scala + + +@pytest.fixture(scope="module") +def scala_ls(): + repo_root = os.path.abspath("test/resources/repos/scala") + logger = LanguageServerLogger(json_format=False, log_level=logging.INFO) + config = LanguageServerConfig(code_language=Language.SCALA) + solidlsp_settings = SolidLSPSettings() + ls = ScalaLanguageServer(config, logger, repo_root, solidlsp_settings) + + with ls.start_server(): + yield ls + + +def test_scala_document_symbols(scala_ls): + """Test document symbols for Main.scala""" + symbols, _ = scala_ls.request_document_symbols(MAIN_FILE_PATH) + symbol_names = [s["name"] for s in symbols] + assert symbol_names[0] == "com.example" + assert symbol_names[1] == "Main" + assert symbol_names[2] == "main" + assert symbol_names[3] == "result" + assert symbol_names[4] == "sum" + assert symbol_names[5] == "add" + + +def test_scala_references_within_same_file(scala_ls): + """Test finding references within the same file.""" + definitions = scala_ls.request_definition(MAIN_FILE_PATH, 12, 23) + first_def = definitions[0] + assert first_def["uri"].endswith("Main.scala") + assert first_def["range"]["start"]["line"] == 16 + assert first_def["range"]["start"]["character"] == 6 + assert first_def["range"]["end"]["line"] == 16 + assert first_def["range"]["end"]["character"] == 9 + + +def test_scala_find_definition_and_references_across_files(scala_ls): + definitions = scala_ls.request_definition(MAIN_FILE_PATH, 8, 25) + assert len(definitions) == 1 + + first_def = definitions[0] + assert first_def["uri"].endswith("Utils.scala") + assert first_def["range"]["start"]["line"] == 7 + assert first_def["range"]["start"]["character"] == 6 + assert first_def["range"]["end"]["line"] == 7 + assert first_def["range"]["end"]["character"] == 14 From f45c22045ab582a466ccbf71480cc9cac8dcf799 Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Sun, 19 Oct 2025 12:55:34 +0200 Subject: [PATCH 017/253] Minor additions to readme, changelog [ci skip] --- CHANGELOG.md | 4 ++++ README.md | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a464f4..91963c29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # latest Status of the `main` branch. Changes prior to the next official version change will appear here. +* Tools: + * Added `RenameSymbolTool` for renaming symbols across the codebase (if LS supports this operation). + * Language support: + * **Add support for Scala** via Metals language server (requires some [manual setup](docs/scala_setup_guide_for_serena.md)) * **Add support for Elm** via @elm-tooling/elm-language-server (automatically downloads if not installed; requires Elm compiler) * **Add support for Perl** via Perl::LanguageServer with LSP integration for .pl, .pm, and .t files * **Add support for AL (Application Language)** for Microsoft Dynamics 365 Business Central development. Requires VS Code AL extension (ms-dynamics-smb.al). diff --git a/README.md b/README.md index f08cd4d0..8e33859f 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,11 @@ With Serena, we provide direct, out-of-the-box support for: * Nix (requires nixd installation) * Elixir (requires installation of NextLS and Elixir; **Windows not supported**) * Elm (automatically downloads elm-language-server if not installed; requires Elm compiler) + * Scala (requires some [manual setup](docs/scala_setup_guide_for_serena.md); uses Metals LSP) * Erlang (requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls), experimental, might be slow or hang) * Perl (requires installation of Perl::LanguageServer) * AL - * Markdown (automatically downloads marksman if not installed, experimental, must be explicitly specified via `--language markdown` when generating project config - primarily useful for documentation-heavy projects) + * Markdown (must be explicitly specified via `--language markdown` when generating project config, primarily useful for documentation-heavy projects) Support for further languages can easily be added by providing a shallow adapter for a new language server implementation, see Serena's [memory on that](.serena/memories/adding_new_language_support_guide.md). @@ -851,7 +852,7 @@ For details on contributing, see [contributing guidelines](/CONTRIBUTING.md). Here is the list of Serena's default tools with a short description (output of `uv run serena tools list`): -* `activate_project`: Activates a project by name. +* `activate_project`: Activates a project based on the project name or path. * `check_onboarding_performed`: Checks whether project onboarding was already performed. * `create_text_file`: Creates/overwrites a file in the project directory. * `delete_memory`: Deletes a memory from Serena's project-specific memory store. @@ -859,6 +860,7 @@ Here is the list of Serena's default tools with a short description (output of ` * `find_file`: Finds files in the given relative paths * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +* `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. @@ -868,6 +870,7 @@ Here is the list of Serena's default tools with a short description (output of ` * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). * `read_file`: Reads a file within the project directory. * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +* `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities. * `replace_regex`: Replaces content in a file by using regular expressions. * `replace_symbol_body`: Replaces the full definition of a symbol. * `search_for_pattern`: Performs a search for a pattern in the project. From d88e2dd180754bcc2346a8767eb69af5ed4e2d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=96=E4=BA=96=E4=BC=8D?= Date: Tue, 21 Oct 2025 03:36:32 +0800 Subject: [PATCH 018/253] Enhancing file read robustness (#682) If a file cannot be read using the project's defined encoding, use charset-normalizer to detect the likely encoding --- src/serena/project.py | 6 +++--- src/solidlsp/ls_utils.py | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index 4a0fae8c..146c5354 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -13,6 +13,7 @@ from serena.util.file_system import GitignoreParser, match_path from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils from solidlsp.settings import SolidLSPSettings log = logging.getLogger(__name__) @@ -138,9 +139,8 @@ class Project: :return: the content of the file """ abs_path = Path(self.project_root) / relative_path - if not abs_path.exists(): - raise FileNotFoundError(f"File not found: {abs_path}") - return abs_path.read_text(encoding=self.project_config.encoding) + ls_logger = LanguageServerLogger(log_level=log.level) + return FileUtils.read_file(ls_logger, str(abs_path), self.project_config.encoding) def get_ignore_spec(self) -> pathspec.PathSpec: """ diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index 3cc124f9..12fd0ddb 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -13,6 +13,7 @@ import zipfile from enum import Enum from pathlib import Path, PurePath +import charset_normalizer import requests from solidlsp.ls_exceptions import SolidLSPException @@ -173,8 +174,19 @@ class FileUtils: logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR) raise SolidLSPException(f"File read '{file_path}' failed: File does not exist.") try: - with open(file_path, encoding=encoding) as inp_file: - return inp_file.read() + try: + with open(file_path, encoding=encoding) as inp_file: + return inp_file.read() + except UnicodeDecodeError as ude: + results = charset_normalizer.from_path(file_path) + match = results.best() + if match: + logger.log( + f"Could not decode {file_path} with encoding='{encoding}'; using best match '{match.encoding}' instead", + logging.WARNING, + ) + return match.raw.decode(match.encoding) + raise ude except Exception as exc: logger.log(f"File read '{file_path}' failed to read with encoding '{encoding}': {exc}", logging.ERROR) raise SolidLSPException("File read failed.") from None From 64ac699ac6c686f953ddf0d8063ecd3b16182f53 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 20 Oct 2025 22:41:47 +0200 Subject: [PATCH 019/253] FileUtils.read_file: Remove logger parameter, do not wrap exceptions --- src/serena/project.py | 3 +-- src/solidlsp/ls.py | 4 ++-- src/solidlsp/ls_utils.py | 20 ++++++++++++-------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index 146c5354..bd444b03 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -139,8 +139,7 @@ class Project: :return: the content of the file """ abs_path = Path(self.project_root) / relative_path - ls_logger = LanguageServerLogger(log_level=log.level) - return FileUtils.read_file(ls_logger, str(abs_path), self.project_config.encoding) + return FileUtils.read_file(str(abs_path), self.project_config.encoding) def get_ignore_spec(self) -> pathspec.PathSpec: """ diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index d4a9c111..849b1256 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -392,7 +392,7 @@ class SolidLanguageServer(ABC): yield self.open_file_buffers[uri] self.open_file_buffers[uri].ref_count -= 1 else: - contents = FileUtils.read_file(self.logger, absolute_file_path, self._encoding) + contents = FileUtils.read_file(absolute_file_path, self._encoding) version = 0 self.open_file_buffers[uri] = LSPFileBuffer(uri, contents, version, self.language_id, 1) @@ -1409,7 +1409,7 @@ class SolidLanguageServer(ABC): # checking if the line is empty, unfortunately ugly and duplicating code, but I don't want to refactor with self.open_file(relative_file_path): absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) - content = FileUtils.read_file(self.logger, absolute_file_path, self._encoding) + content = FileUtils.read_file(absolute_file_path, self._encoding) if content.split("\n")[line].strip() == "": self.logger.log( f"Passing empty lines to request_container_symbol is currently not supported, {relative_file_path=}, {line=}", diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index 12fd0ddb..0cec5994 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -20,6 +20,8 @@ from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_types import UnifiedSymbolInformation +log = logging.getLogger(__name__) + class InvalidTextLocationError(Exception): pass @@ -166,13 +168,16 @@ class FileUtils: """ @staticmethod - def read_file(logger: LanguageServerLogger, file_path: str, encoding: str) -> str: + def read_file(file_path: str, encoding: str) -> str: """ - Reads the file at the given path and returns the contents as a string. + Reads the file at the given path using the given encoding and returns the contents as a string. + If decoding fails, tries to detect the encoding using charset_normalizer. + + Raises FileNotFoundError if the file does not exist. """ if not os.path.exists(file_path): - logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR) - raise SolidLSPException(f"File read '{file_path}' failed: File does not exist.") + log.error(f"Failed to read '{file_path}': File does not exist.") + raise FileNotFoundError(f"File read '{file_path}' failed: File does not exist.") try: try: with open(file_path, encoding=encoding) as inp_file: @@ -181,15 +186,14 @@ class FileUtils: results = charset_normalizer.from_path(file_path) match = results.best() if match: - logger.log( + log.warning( f"Could not decode {file_path} with encoding='{encoding}'; using best match '{match.encoding}' instead", - logging.WARNING, ) return match.raw.decode(match.encoding) raise ude except Exception as exc: - logger.log(f"File read '{file_path}' failed to read with encoding '{encoding}': {exc}", logging.ERROR) - raise SolidLSPException("File read failed.") from None + log.error(f"Failed to read '{file_path}' with encoding '{encoding}': {exc}") + raise exc @staticmethod def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None: From fd410a46388bf28509093a43422fb357329bd6ea Mon Sep 17 00:00:00 2001 From: Vishwajit Sarnobat <128615348+vishwajitsarnobat@users.noreply.github.com> Date: Wed, 22 Oct 2025 04:07:12 +0530 Subject: [PATCH 020/253] feat: Add Julia language server support (#691) Implements JuliaLanguageServer with LanguageServer.jl integration. Includes comprehensive tests and auto-detection of Julia installations. Checks for LanguageServer.jl on startup and installs via Pkg.add if not found. Only runs once on first use. --- .github/workflows/pytest.yml | 7 + .gitignore | 3 + pyproject.toml | 1 + src/solidlsp/language_servers/julia_server.py | 181 ++++++++++++++++++ src/solidlsp/ls_config.py | 7 + .../repos/julia/test_repo/lib/helper.jl | 5 + test/resources/repos/julia/test_repo/main.jl | 13 ++ test/solidlsp/julia/test_julia_basic.py | 50 +++++ 8 files changed, 267 insertions(+) create mode 100644 src/solidlsp/language_servers/julia_server.py create mode 100644 test/resources/repos/julia/test_repo/lib/helper.jl create mode 100644 test/resources/repos/julia/test_repo/main.jl create mode 100644 test/solidlsp/julia/test_julia_basic.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 7936dbc9..6261a79e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -147,6 +147,13 @@ jobs: shell: bash run: | Rscript -e "install.packages('languageserver', repos='https://cloud.r-project.org')" + - name: Set up Julia + uses: julia-actions/setup-julia@v2 + with: + version: '1.10' + - name: Install Julia LanguageServer + shell: bash + run: julia -e 'using Pkg; Pkg.add("LanguageServer")' - name: Install Zig uses: goto-bus-stop/setup-zig@v2 with: diff --git a/.gitignore b/.gitignore index 3809efdb..32386b39 100644 --- a/.gitignore +++ b/.gitignore @@ -231,6 +231,9 @@ tmp/ # Exception: Don't ignore Nix test repository lib directory (contains source code) !/test/resources/repos/nix/test_repo/lib +# Exception: Don't ignore Julia test repository lib directory (contains source code) +!/test/resources/repos/julia/test_repo/lib + # Swift /test/resources/repos/swift/test_repo/.build /test/resources/repos/swift/test_repo/.swiftpm diff --git a/pyproject.toml b/pyproject.toml index b8e62d81..d6d222bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -280,6 +280,7 @@ markers = [ "al: language server running for AL (Microsoft Dynamics 365 Business Central)", "rego: language server running for Rego", "markdown: language server running for Markdown", + "julia: Julia language server tests", ] [tool.codespell] diff --git a/src/solidlsp/language_servers/julia_server.py b/src/solidlsp/language_servers/julia_server.py new file mode 100644 index 00000000..05a0cbbb --- /dev/null +++ b/src/solidlsp/language_servers/julia_server.py @@ -0,0 +1,181 @@ +import logging +import os +import pathlib +import platform +import shutil +import subprocess + +from overrides import override + +from solidlsp.ls import SolidLanguageServer +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.settings import SolidLSPSettings + + +class JuliaLanguageServer(SolidLanguageServer): + """ + Language server implementation for Julia using LanguageServer.jl. + """ + + def __init__( + self, + config: LanguageServerConfig, + logger: LanguageServerLogger, + repository_root_path: str, + solidlsp_settings: SolidLSPSettings, + ): + julia_executable = self._setup_runtime_dependency(logger) # PASS LOGGER + julia_code = "using LanguageServer; runserver()" + + if platform.system() == "Windows": + # On Windows, pass as list (Serena handles shell=True differently) + julia_ls_cmd = [julia_executable, "--startup-file=no", "--history-file=no", "-e", julia_code, repository_root_path] + else: + # On Linux/macOS, build shell-escaped string + import shlex + + julia_ls_cmd = ( + f"{shlex.quote(julia_executable)} " + f"--startup-file=no " + f"--history-file=no " + f"-e {shlex.quote(julia_code)} " + f"{shlex.quote(repository_root_path)}" + ) + + logger.log(f"[JULIA DEBUG] Command: {julia_ls_cmd}", logging.INFO) + + super().__init__( + config, + logger, + repository_root_path, + ProcessLaunchInfo(cmd=julia_ls_cmd, cwd=repository_root_path), + "julia", + solidlsp_settings, + ) + + @staticmethod + def _setup_runtime_dependency(logger: LanguageServerLogger) -> str: + """ + Check if the Julia runtime is available and return its full path. + Raises RuntimeError with a helpful message if the dependency is missing. + """ + # First check if julia is in PATH + julia_path = shutil.which("julia") + + # If not found in PATH, check common installation locations + if julia_path is None: + common_locations = [ + os.path.expanduser("~/.juliaup/bin/julia"), + os.path.expanduser("~/.julia/bin/julia"), + "/usr/local/bin/julia", + "/usr/bin/julia", + ] + + for location in common_locations: + if os.path.isfile(location) and os.access(location, os.X_OK): + julia_path = location + break + + if julia_path is None: + raise RuntimeError( + "Julia is not installed or not in your PATH. " + "Please install Julia from https://julialang.org/downloads/ and ensure it is accessible. " + f"Checked locations: {common_locations}" + ) + + # Check if LanguageServer.jl is installed + check_cmd = [julia_path, "-e", "using LanguageServer"] + try: + result = subprocess.run(check_cmd, check=False, capture_output=True, text=True, timeout=10) + if result.returncode != 0: + # LanguageServer.jl not found, install it + JuliaLanguageServer._install_language_server(julia_path, logger) + except subprocess.TimeoutExpired: + # Assume it needs installation + JuliaLanguageServer._install_language_server(julia_path, logger) + + return julia_path + + @staticmethod + def _install_language_server(julia_path: str, logger: LanguageServerLogger) -> None: + """Install LanguageServer.jl package.""" + logger.log("LanguageServer.jl not found. Installing... (this may take a minute)", logging.INFO) + + install_cmd = [julia_path, "-e", 'using Pkg; Pkg.add("LanguageServer")'] + + try: + result = subprocess.run(install_cmd, check=False, capture_output=True, text=True, timeout=300) # 5 minutes for installation + + if result.returncode == 0: + logger.log("LanguageServer.jl installed successfully!", logging.INFO) + else: + raise RuntimeError(f"Failed to install LanguageServer.jl: {result.stderr}") + except subprocess.TimeoutExpired: + raise RuntimeError( + "LanguageServer.jl installation timed out. Please install manually: julia -e 'using Pkg; Pkg.add(\"LanguageServer\")'" + ) + + @override + def is_ignored_dirname(self, dirname: str) -> bool: + """Define language-specific directories to ignore for Julia projects.""" + return super().is_ignored_dirname(dirname) or dirname in [".julia", "build", "dist"] + + def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: + """ + Returns the initialize params for the Julia Language Server. + """ + root_uri = pathlib.Path(repository_absolute_path).as_uri() + initialize_params: InitializeParams = { # type: ignore + "processId": os.getpid(), + "rootPath": repository_absolute_path, + "rootUri": root_uri, + "capabilities": { + "workspace": {"workspaceFolders": True}, + "textDocument": { + "definition": {"dynamicRegistration": True}, + "references": {"dynamicRegistration": True}, + "documentSymbol": {"dynamicRegistration": True}, + }, + }, + "workspaceFolders": [ + { + "uri": root_uri, + "name": os.path.basename(repository_absolute_path), + } + ], + } + return initialize_params + + def _start_server(self): + """Start the LanguageServer.jl server process.""" + + def do_nothing(params): + return + + def window_log_message(msg): + self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) + + self.server.on_notification("window/logMessage", window_log_message) + self.server.on_notification("$/progress", do_nothing) + self.server.on_notification("textDocument/publishDiagnostics", do_nothing) + + self.logger.log("Starting LanguageServer.jl server process", logging.INFO) + self.server.start() + + initialize_params = self._get_initialize_params(self.repository_root_path) + self.logger.log( + "Sending initialize request to Julia Language Server", + logging.INFO, + ) + + init_response = self.server.send.initialize(initialize_params) + assert "definitionProvider" in init_response["capabilities"] + assert "referencesProvider" in init_response["capabilities"] + assert "documentSymbolProvider" in init_response["capabilities"] + + self.server.notify.initialized({}) + self.completions_available.set() + self.logger.log("Julia Language Server is initialized and ready.", logging.INFO) diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index ca76ae72..8c667cba 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -57,6 +57,7 @@ class Language(str, Enum): AL = "al" REGO = "rego" SCALA = "scala" + JULIA = "julia" # Experimental or deprecated Language Servers TYPESCRIPT_VTS = "typescript_vts" """Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls""" @@ -155,6 +156,8 @@ class Language(str, Enum): return FilenameMatcher("*.md", "*.markdown") case self.SCALA: return FilenameMatcher("*.scala", "*.sbt") + case self.JULIA: + return FilenameMatcher("*.jl") case _: raise ValueError(f"Unhandled language: {self}") @@ -284,6 +287,10 @@ class Language(str, Enum): from solidlsp.language_servers.scala_language_server import ScalaLanguageServer return ScalaLanguageServer + case self.JULIA: + from solidlsp.language_servers.julia_server import JuliaLanguageServer + + return JuliaLanguageServer case _: raise ValueError(f"Unhandled language: {self}") diff --git a/test/resources/repos/julia/test_repo/lib/helper.jl b/test/resources/repos/julia/test_repo/lib/helper.jl new file mode 100644 index 00000000..c6a7e120 --- /dev/null +++ b/test/resources/repos/julia/test_repo/lib/helper.jl @@ -0,0 +1,5 @@ +module Helper + function say_hello() + println("Hello from the helper module!") + end +end diff --git a/test/resources/repos/julia/test_repo/main.jl b/test/resources/repos/julia/test_repo/main.jl new file mode 100644 index 00000000..fa317977 --- /dev/null +++ b/test/resources/repos/julia/test_repo/main.jl @@ -0,0 +1,13 @@ +include("lib/helper.jl") + +function calculate_sum(a, b) + return a + b +end + +function main() + result = calculate_sum(5, 3) # A within-file reference + println(result) + Helper.say_hello() # A cross-file reference +end + +main() diff --git a/test/solidlsp/julia/test_julia_basic.py b/test/solidlsp/julia/test_julia_basic.py new file mode 100644 index 00000000..e411f3d8 --- /dev/null +++ b/test/solidlsp/julia/test_julia_basic.py @@ -0,0 +1,50 @@ +import pytest + +from solidlsp.ls import SolidLanguageServer +from solidlsp.ls_config import Language + + +@pytest.mark.julia +class TestJuliaLanguageServer: + @pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True) + def test_julia_symbols(self, language_server: SolidLanguageServer): + """ + Test if we can find the top-level symbols in the main.jl file. + """ + all_symbols, _ = language_server.request_document_symbols("main.jl") + symbol_names = {s["name"] for s in all_symbols} + assert "calculate_sum" in symbol_names + assert "main" in symbol_names + + @pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True) + def test_julia_within_file_references(self, language_server: SolidLanguageServer): + """ + Test finding references to a function within the same file. + """ + # Find references to 'calculate_sum' - the function name starts at line 2, column 9 + # LSP uses 0-based indexing + references = language_server.request_references("main.jl", line=2, column=9) + + # Should find at least the definition and the call site + assert len(references) >= 1, f"Expected at least 1 reference, got {len(references)}" + + # Verify at least one reference is in main.jl + reference_paths = [ref["relativePath"] for ref in references] + assert "main.jl" in reference_paths + + @pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True) + def test_julia_cross_file_references(self, language_server: SolidLanguageServer): + """ + Test finding references to a function defined in another file. + """ + # The 'say_hello' function name starts at line 1, column 13 in lib/helper.jl + # LSP uses 0-based indexing + references = language_server.request_references("lib/helper.jl", line=1, column=13) + + # Should find at least the call site in main.jl + assert len(references) >= 1, f"Expected at least 1 reference, got {len(references)}" + + # Verify at least one reference points to the usage + reference_paths = [ref["relativePath"] for ref in references] + # The reference might be in either file (definition or usage) + assert "main.jl" in reference_paths or "lib/helper.jl" in reference_paths From 1e06812d685489e732e2d726184873b70fe36190 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 23 Oct 2025 12:26:31 +0200 Subject: [PATCH 021/253] Fix: Allow increasing the log level (e.g. to WARNING or ERROR) --- src/serena/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 3c15b8fb..dfe8c1ad 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -90,7 +90,7 @@ class SerenaAgent: # adjust log level serena_log_level = self.serena_config.log_level - if Logger.root.level > serena_log_level: + if Logger.root.level != serena_log_level: log.info(f"Changing the root logger level to {serena_log_level}") Logger.root.setLevel(serena_log_level) From 570703d0ae82fe7601b3aa46226a3dc2725a8e9b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 24 Oct 2025 22:21:10 +0200 Subject: [PATCH 022/253] Improve string representation of Project and JetBrainsSymbol Improve typing of JetBrainsSymbol --- src/serena/project.py | 9 ++++++++- src/serena/symbol.py | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index bd444b03..e96266cf 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import Any import pathspec +from sensai.util.string import ToStringMixin from serena.config.serena_config import DEFAULT_TOOL_TIMEOUT, ProjectConfig, get_serena_managed_in_project_dir from serena.constants import SERENA_FILE_ENCODING, SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME @@ -53,7 +54,7 @@ class MemoriesManager: return f"Memory {name} deleted." -class Project: +class Project(ToStringMixin): def __init__(self, project_root: str, project_config: ProjectConfig, is_newly_created: bool = False): self.project_root = project_root self.project_config = project_config @@ -90,6 +91,12 @@ class Project: log.debug(f"Processing {len(processed_patterns)} ignored paths") self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, processed_patterns) + def _tostring_includes(self) -> list[str]: + return [] + + def _tostring_additional_entries(self) -> dict[str, Any]: + return {"root": self.project_root, "name": self.project_name} + @property def project_name(self) -> str: return self.project_config.project_name diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 9d89c5fb..134b217e 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -4,7 +4,7 @@ import os from abc import ABC, abstractmethod from collections.abc import Iterator, Sequence from dataclasses import asdict, dataclass -from typing import TYPE_CHECKING, Any, Self, Union +from typing import TYPE_CHECKING, Any, NotRequired, Self, TypedDict, Union from sensai.util.string import ToStringMixin @@ -77,7 +77,7 @@ class PositionInFile: return Position(line=self.line, character=self.col) -class Symbol(ABC): +class Symbol(ToStringMixin, ABC): @abstractmethod def get_body_start_position(self) -> PositionInFile | None: pass @@ -599,7 +599,15 @@ class LanguageServerSymbolRetriever: class JetBrainsSymbol(Symbol): - def __init__(self, symbol_dict: dict, project: Project) -> None: + class SymbolDict(TypedDict): + name_path: str + relative_path: str + type: str + text_range: NotRequired[dict] + body: NotRequired[str] + children: NotRequired[list["JetBrainsSymbol.SymbolDict"]] + + def __init__(self, symbol_dict: SymbolDict, project: Project) -> None: """ :param symbol_dict: dictionary as returned by the JetBrains plugin client. """ @@ -609,6 +617,15 @@ class JetBrainsSymbol(Symbol): self._cached_body_start_position: PositionInFile | None = None self._cached_body_end_position: PositionInFile | None = None + def _tostring_includes(self) -> list[str]: + return [] + + def _tostring_additional_entries(self) -> dict[str, Any]: + return dict(name_path=self.get_name_path(), relative_path=self.get_relative_path(), type=self._dict["type"]) + + def get_name_path(self) -> str: + return self._dict["name_path"] + def get_relative_path(self) -> str: return self._dict["relative_path"] From 70ab5e77bf544176444a8b84c6017126c4adfcc9 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 25 Oct 2025 21:30:19 +0200 Subject: [PATCH 023/253] Re-enable InitialInstructionsTool, because some clients do not read the system prompt at connection time (e.g. Claude Desktop) Establish a "name" for the instructions (Serena Instructions Manual) such that the agent can know whether it has read the instructions and therefore will know whether or not the tool needs to be called --- .../config/prompt_templates/system_prompt.yml | 2 ++ src/serena/tools/workflow_tools.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml index 9b4f7a12..ce47c3e5 100644 --- a/src/serena/resources/config/prompt_templates/system_prompt.yml +++ b/src/serena/resources/config/prompt_templates/system_prompt.yml @@ -64,3 +64,5 @@ prompts: {% for prompt in mode_system_prompts %} - {{ prompt }} {% endfor %} + + You have hereby read the 'Serena Instructions Manual' and do not need to read it again. diff --git a/src/serena/tools/workflow_tools.py b/src/serena/tools/workflow_tools.py index be78d03d..5961a397 100644 --- a/src/serena/tools/workflow_tools.py +++ b/src/serena/tools/workflow_tools.py @@ -122,18 +122,17 @@ class PrepareForNewConversationTool(Tool): return self.prompt_factory.create_prepare_for_new_conversation() -class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional): +class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject): """ - Gets the initial instructions for the current project. - Should only be used in settings where the system prompt cannot be set, - e.g. in clients you have no control over, like Claude Desktop. + Provides instructions on how to use the Serena toolbox. + Should only be used in settings where the system prompt is not read automatically by the client. + + NOTE: Some MCP clients (including Claude Desktop) do not read the system prompt automatically! """ def apply(self) -> str: """ - Get the initial instructions for the current coding project. - If you haven't received instructions on how to use Serena's tools in the system prompt, - you should always call this tool before starting to work (including using any other tool) on any programming task, - the only exception being when you are asked to call `activate_project`, which you should then call before. + Provides the 'Serena Instructions Manual', which contains essential information on how to use the Serena toolbox. + Call this tool if you have not yet read this very important manual! """ return self.agent.create_system_prompt() From 1461aff18c5010a711c286d0401fb86729088050 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 26 Oct 2025 12:00:00 +0100 Subject: [PATCH 024/253] Improve description of RegexReplaceTool --- src/serena/tools/file_tools.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 38cfd2f7..6a7479bf 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -171,6 +171,8 @@ class ReplaceRegexTool(Tool, ToolMarkerCanEdit): ) -> str: r""" Replaces one or more occurrences of the given regular expression. + Works exactly like Python's re.subn function with flags DOTALL and MULTILINE enabled. + This is the preferred way to replace content in a file whenever the symbol-level tools are not appropriate. Even large sections of code can be replaced by providing a concise regular expression of @@ -183,10 +185,11 @@ class ReplaceRegexTool(Tool, ToolMarkerCanEdit): :param relative_path: the relative path to the file :param regex: a Python-style regular expression, matches of which will be replaced. Dot matches all characters, multi-line matching is enabled. + Apply the usual escaping as needed for reserved characters in Python-style regex. :param repl: the string to replace the matched content with, which may contain - backreferences like \1, \2, etc. - IMPORTANT: Make sure to escape special characters appropriately! - Use "\n" to insert a newline, but use "\\n" to insert the string "\n" within a string literal. + backreferences like \1, \2, etc. for groups matched by the regex. + Insert new content verbatim, except for backslashes, which have to be escaped. + IMPORTANT: When inserting a quoted string with newlines encoded as backslash followed by n, don't forget to insert double backslashes (i.e., \\n). :param allow_multiple_occurrences: if True, the regex may match multiple occurrences in the file and all of them will be replaced. If this is set to False and the regex matches multiple occurrences, an error will be returned From 37c4898bd6781781107c910d54a1e9740570bd0f Mon Sep 17 00:00:00 2001 From: Ketema Harris Date: Sun, 26 Oct 2025 18:30:40 -0400 Subject: [PATCH 025/253] feat: Add Haskell language server support (#697) --- .github/workflows/pytest.yml | 34 ++ .gitignore | 10 +- CHANGELOG.md | 1 + README.md | 1 + pyproject.toml | 1 + .../haskell_language_server.py | 388 ++++++++++++++++++ src/solidlsp/ls_config.py | 7 + .../repos/haskell/test_repo/app/Main.hs | 26 ++ .../haskell/test_repo/haskell-test-repo.cabal | 45 ++ .../repos/haskell/test_repo/package.yaml | 25 ++ .../repos/haskell/test_repo/src/Calculator.hs | 43 ++ .../repos/haskell/test_repo/src/Helper.hs | 22 + .../repos/haskell/test_repo/stack.yaml | 5 + test/solidlsp/haskell/__init__.py | 1 + test/solidlsp/haskell/test_haskell_basic.py | 219 ++++++++++ 15 files changed, 827 insertions(+), 1 deletion(-) create mode 100644 src/solidlsp/language_servers/haskell_language_server.py create mode 100644 test/resources/repos/haskell/test_repo/app/Main.hs create mode 100644 test/resources/repos/haskell/test_repo/haskell-test-repo.cabal create mode 100644 test/resources/repos/haskell/test_repo/package.yaml create mode 100644 test/resources/repos/haskell/test_repo/src/Calculator.hs create mode 100644 test/resources/repos/haskell/test_repo/src/Helper.hs create mode 100644 test/resources/repos/haskell/test_repo/stack.yaml create mode 100644 test/solidlsp/haskell/__init__.py create mode 100644 test/solidlsp/haskell/test_haskell_basic.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 6261a79e..22209e48 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -154,6 +154,40 @@ jobs: - name: Install Julia LanguageServer shell: bash run: julia -e 'using Pkg; Pkg.add("LanguageServer")' + - name: Setup Haskell toolchain + if: runner.os != 'Windows' + uses: haskell/ghcup-setup@v1 + with: + ghc: '9.12.2' + cabal: '3.10.3.0' + hls: '2.11.0.0' + - name: Verify Haskell tools + if: runner.os != 'Windows' + run: | + echo "Verifying installed Haskell tools:" + which ghc && ghc --version + which cabal && cabal --version + # HLS verification - non-blocking in case of version incompatibility + if command -v haskell-language-server-wrapper &>/dev/null; then + echo "Found haskell-language-server-wrapper" + haskell-language-server-wrapper --version || echo "WARNING: HLS wrapper found but version check failed" + elif command -v haskell-language-server &>/dev/null; then + echo "Found haskell-language-server" + haskell-language-server --version || echo "WARNING: HLS found but version check failed" + else + echo "WARNING: HLS not found (may be incompatible with GHC 9.12.2)" + echo "This is not a critical error - tests will use HLS if available at runtime" + fi + shell: bash + - name: Pre-build Haskell test project for HLS + if: runner.os != 'Windows' + run: | + cd test/resources/repos/haskell/test_repo + cabal update + cabal build --only-dependencies + cabal build + echo "Haskell test project built successfully" + shell: bash - name: Install Zig uses: goto-bus-stop/setup-zig@v2 with: diff --git a/.gitignore b/.gitignore index 32386b39..f71c7714 100644 --- a/.gitignore +++ b/.gitignore @@ -248,4 +248,12 @@ tmp/ .scala-build/ .bloop/ bootstrap -test/resources/repos/scala/.bloop/ \ No newline at end of file +test/resources/repos/scala/.bloop/ + +# Haskell +.stack-work/ +*.cabal +stack.yaml.lock +dist-newstyle/ +cabal.project.local* +.ghc.environment.* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 91963c29..d7d323eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Status of the `main` branch. Changes prior to the next official version change w * Added `RenameSymbolTool` for renaming symbols across the codebase (if LS supports this operation). * Language support: + * **Add support for Haskell** via Haskell Language Server (HLS) with automatic discovery via ghcup, stack, or system PATH; supports both Stack and Cabal projects * **Add support for Scala** via Metals language server (requires some [manual setup](docs/scala_setup_guide_for_serena.md)) * **Add support for Elm** via @elm-tooling/elm-language-server (automatically downloads if not installed; requires Elm compiler) * **Add support for Perl** via Perl::LanguageServer with LSP integration for .pl, .pm, and .t files diff --git a/README.md b/README.md index 8e33859f..f6a1faea 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ With Serena, we provide direct, out-of-the-box support for: * Scala (requires some [manual setup](docs/scala_setup_guide_for_serena.md); uses Metals LSP) * Erlang (requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls), experimental, might be slow or hang) * Perl (requires installation of Perl::LanguageServer) + * Haskell (automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects) * AL * Markdown (must be explicitly specified via `--language markdown` when generating project config, primarily useful for documentation-heavy projects) diff --git a/pyproject.toml b/pyproject.toml index d6d222bc..dde69b81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -281,6 +281,7 @@ markers = [ "rego: language server running for Rego", "markdown: language server running for Markdown", "julia: Julia language server tests", + "haskell: Haskell language server tests", ] [tool.codespell] diff --git a/src/solidlsp/language_servers/haskell_language_server.py b/src/solidlsp/language_servers/haskell_language_server.py new file mode 100644 index 00000000..1db2dc6a --- /dev/null +++ b/src/solidlsp/language_servers/haskell_language_server.py @@ -0,0 +1,388 @@ +""" +Provides Haskell specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Haskell. +""" + +import logging +import os +import pathlib +import shutil +import time + +from overrides import override + +from solidlsp.ls import SolidLanguageServer +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.settings import SolidLSPSettings + + +class HaskellLanguageServer(SolidLanguageServer): + """ + Provides Haskell specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Haskell. + Uses Haskell Language Server (HLS) for LSP functionality. + """ + + @staticmethod + def _ensure_hls_installed(): + """Ensure haskell-language-server-wrapper is available.""" + # Try common locations + common_paths = [ + shutil.which("haskell-language-server-wrapper"), + "/opt/homebrew/bin/haskell-language-server-wrapper", + "/usr/local/bin/haskell-language-server-wrapper", + os.path.expanduser("~/.ghcup/bin/haskell-language-server-wrapper"), + os.path.expanduser("~/.cabal/bin/haskell-language-server-wrapper"), + os.path.expanduser("~/.local/bin/haskell-language-server-wrapper"), + ] + + # Check Stack programs directory + stack_programs = os.path.expanduser("~/.local/share/stack/programs") + if os.path.exists(stack_programs): + try: + for arch_dir in os.listdir(stack_programs): + arch_path = os.path.join(stack_programs, arch_dir) + if os.path.isdir(arch_path): + try: + for ghc_dir in os.listdir(arch_path): + hls_path = os.path.join(arch_path, ghc_dir, "bin", "haskell-language-server-wrapper") + if os.path.isfile(hls_path) and os.access(hls_path, os.X_OK): + common_paths.append(hls_path) + except (PermissionError, OSError): + # Skip directories we can't read + continue + except (PermissionError, OSError): + # Stack programs directory not accessible + pass + + for path in common_paths: + if path and os.path.isfile(path) and os.access(path, os.X_OK): + return path + + raise RuntimeError( + "haskell-language-server-wrapper is not installed or not in PATH.\n" + "Searched locations:\n" + "\n".join(f" - {p}" for p in common_paths if p) + "\n" + "Please install HLS via:\n" + " - GHCup: https://www.haskell.org/ghcup/\n" + " - Stack: stack install haskell-language-server\n" + " - Cabal: cabal install haskell-language-server\n" + " - Homebrew (macOS): brew install haskell-language-server" + ) + + def __init__( + self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings + ): + """ + Creates a HaskellLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. + """ + hls_executable_path = self._ensure_hls_installed() + logger.log(f"Using haskell-language-server at: {hls_executable_path}", logging.INFO) + + # Check if there's a haskell subdirectory with Stack/Cabal project + haskell_subdir = os.path.join(repository_root_path, "haskell") + if os.path.exists(haskell_subdir) and ( + os.path.exists(os.path.join(haskell_subdir, "stack.yaml")) or os.path.exists(os.path.join(haskell_subdir, "cabal.project")) + ): + working_dir = haskell_subdir + logger.log(f"Using Haskell project directory: {working_dir}", logging.INFO) + else: + working_dir = repository_root_path + + # Set up environment with GHCup bin in PATH + env = dict(os.environ) + ghcup_bin = os.path.expanduser("~/.ghcup/bin") + if ghcup_bin not in env.get("PATH", ""): + env["PATH"] = f"{ghcup_bin}{os.pathsep}{env.get('PATH', '')}" + + super().__init__( + config, + logger, + repository_root_path, + ProcessLaunchInfo(cmd=[hls_executable_path, "--lsp", "--cwd", working_dir], cwd=working_dir, env=env), + "haskell", + solidlsp_settings, + ) + + @override + def is_ignored_dirname(self, dirname: str) -> bool: + return super().is_ignored_dirname(dirname) or dirname in ["dist", "dist-newstyle", ".stack-work", ".cabal-sandbox"] + + @staticmethod + def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: + """ + Returns the initialize params for the Haskell Language Server. + """ + root_uri = pathlib.Path(repository_absolute_path).as_uri() + initialize_params = { + "clientInfo": {"name": "Serena", "version": "0.1.0"}, + "locale": "en", + "capabilities": { + "workspace": { + "applyEdit": True, + "workspaceEdit": { + "documentChanges": True, + "resourceOperations": ["create", "rename", "delete"], + "failureHandling": "textOnlyTransactional", + "normalizesLineEndings": True, + "changeAnnotationSupport": {"groupsOnLabel": True}, + }, + "configuration": True, + "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True}, + "symbol": { + "dynamicRegistration": True, + "symbolKind": {"valueSet": list(range(1, 27))}, + "tagSupport": {"valueSet": [1]}, + "resolveSupport": {"properties": ["location.range"]}, + }, + "executeCommand": {"dynamicRegistration": True}, + "didChangeConfiguration": {"dynamicRegistration": True}, + "workspaceFolders": True, + "semanticTokens": {"refreshSupport": True}, + }, + "textDocument": { + "publishDiagnostics": { + "relatedInformation": True, + "versionSupport": False, + "tagSupport": {"valueSet": [1, 2]}, + "codeDescriptionSupport": True, + "dataSupport": True, + }, + "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True}, + "completion": { + "dynamicRegistration": True, + "contextSupport": True, + "completionItem": { + "snippetSupport": True, + "commitCharactersSupport": True, + "documentationFormat": ["markdown", "plaintext"], + "deprecatedSupport": True, + "preselectSupport": True, + "tagSupport": {"valueSet": [1]}, + "insertReplaceSupport": True, + "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]}, + "insertTextModeSupport": {"valueSet": [1, 2]}, + "labelDetailsSupport": True, + }, + "insertTextMode": 2, + "completionItemKind": { + "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] + }, + }, + "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, + "signatureHelp": { + "dynamicRegistration": True, + "signatureInformation": { + "documentationFormat": ["markdown", "plaintext"], + "parameterInformation": {"labelOffsetSupport": True}, + "activeParameterSupport": True, + }, + "contextSupport": True, + }, + "definition": {"dynamicRegistration": True, "linkSupport": True}, + "references": {"dynamicRegistration": True}, + "documentHighlight": {"dynamicRegistration": True}, + "documentSymbol": { + "dynamicRegistration": True, + "symbolKind": {"valueSet": list(range(1, 27))}, + "hierarchicalDocumentSymbolSupport": True, + "tagSupport": {"valueSet": [1]}, + "labelSupport": True, + }, + "codeAction": { + "dynamicRegistration": True, + "isPreferredSupport": True, + "disabledSupport": True, + "dataSupport": True, + "resolveSupport": {"properties": ["edit"]}, + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports", + ] + } + }, + "honorsChangeAnnotations": False, + }, + "formatting": {"dynamicRegistration": True}, + "rangeFormatting": {"dynamicRegistration": True}, + "onTypeFormatting": {"dynamicRegistration": True}, + "rename": { + "dynamicRegistration": True, + "prepareSupport": True, + "prepareSupportDefaultBehavior": 1, + "honorsChangeAnnotations": True, + }, + "documentLink": {"dynamicRegistration": True, "tooltipSupport": True}, + "typeDefinition": {"dynamicRegistration": True, "linkSupport": True}, + "implementation": {"dynamicRegistration": True, "linkSupport": True}, + "colorProvider": {"dynamicRegistration": True}, + "foldingRange": { + "dynamicRegistration": True, + "rangeLimit": 5000, + "lineFoldingOnly": True, + "foldingRangeKind": {"valueSet": ["comment", "imports", "region"]}, + }, + "declaration": {"dynamicRegistration": True, "linkSupport": True}, + "selectionRange": {"dynamicRegistration": True}, + "callHierarchy": {"dynamicRegistration": True}, + "semanticTokens": { + "dynamicRegistration": True, + "tokenTypes": [ + "namespace", + "type", + "class", + "enum", + "interface", + "struct", + "typeParameter", + "parameter", + "variable", + "property", + "enumMember", + "event", + "function", + "method", + "macro", + "keyword", + "modifier", + "comment", + "string", + "number", + "regexp", + "operator", + ], + "tokenModifiers": [ + "declaration", + "definition", + "readonly", + "static", + "deprecated", + "abstract", + "async", + "modification", + "documentation", + "defaultLibrary", + ], + "formats": ["relative"], + "requests": {"range": True, "full": {"delta": True}}, + "multilineTokenSupport": False, + "overlappingTokenSupport": False, + }, + "linkedEditingRange": {"dynamicRegistration": True}, + }, + "window": { + "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}}, + "showDocument": {"support": True}, + "workDoneProgress": True, + }, + "general": { + "staleRequestSupport": {"cancel": True, "retryOnContentModified": []}, + "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"}, + "markdown": { + "parser": "marked", + "version": "1.1.0", + }, + "positionEncodings": ["utf-16"], + }, + }, + "initializationOptions": { + "haskell": { + "formattingProvider": "ormolu", + "checkProject": True, + } + }, + "trace": "verbose", + "processId": os.getpid(), + "rootPath": repository_absolute_path, + "rootUri": root_uri, + "workspaceFolders": [ + { + "uri": root_uri, + "name": os.path.basename(repository_absolute_path), + } + ], + } + return initialize_params + + def _start_server(self): + """ + Starts the Haskell Language Server + """ + + def do_nothing(params): + return + + def window_log_message(msg): + self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) + + def register_capability_handler(params): + """Handle dynamic capability registration from HLS""" + if "registrations" in params: + for registration in params.get("registrations", []): + method = registration.get("method", "") + self.logger.log(f"HLS registered capability: {method}", logging.INFO) + return + + def workspace_configuration_handler(params): + """Handle workspace/configuration requests from HLS""" + self.logger.log(f"HLS requesting configuration: {params}", logging.INFO) + + # Configuration matching VS Code settings and initialization options + haskell_config = { + "formattingProvider": "ormolu", + "checkProject": True, + "plugin": {"importLens": {"codeActionsOn": False, "codeLensOn": False}, "hlint": {"codeActionsOn": False}}, + } + + # HLS expects array of config items matching requested sections + if isinstance(params, dict) and "items" in params: + result = [] + for item in params["items"]: + section = item.get("section", "") + if section == "haskell": + result.append(haskell_config) + else: + result.append({}) + self.logger.log(f"Returning configuration: {result}", logging.INFO) + return result + + # Fallback: return single config + return [haskell_config] + + self.server.on_notification("window/logMessage", window_log_message) + self.server.on_notification("$/progress", do_nothing) + self.server.on_notification("textDocument/publishDiagnostics", do_nothing) + self.server.on_request("client/registerCapability", register_capability_handler) + self.server.on_request("workspace/configuration", workspace_configuration_handler) + + self.logger.log("Starting Haskell Language Server process", logging.INFO) + self.server.start() + initialize_params = self._get_initialize_params(self.repository_root_path) + + self.logger.log( + "Sending initialize request from LSP client to LSP server and awaiting response", + logging.INFO, + ) + init_response = self.server.send.initialize(initialize_params) + + # Log capabilities returned by HLS + capabilities = init_response.get("capabilities", {}) + self.logger.log(f"HLS capabilities: {list(capabilities.keys())}", logging.INFO) + + self.server.notify.initialized({}) + self.completions_available.set() + + # Give HLS time to index the project + # HLS can be slow to index, especially on first run + self.logger.log("Waiting for HLS to index project...", logging.INFO) + time.sleep(5) + + self.logger.log("Haskell Language Server initialized successfully", logging.INFO) diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index 8c667cba..741ab43e 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -58,6 +58,7 @@ class Language(str, Enum): REGO = "rego" SCALA = "scala" JULIA = "julia" + HASKELL = "haskell" # Experimental or deprecated Language Servers TYPESCRIPT_VTS = "typescript_vts" """Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls""" @@ -158,6 +159,8 @@ class Language(str, Enum): return FilenameMatcher("*.scala", "*.sbt") case self.JULIA: return FilenameMatcher("*.jl") + case self.HASKELL: + return FilenameMatcher("*.hs", "*.lhs") case _: raise ValueError(f"Unhandled language: {self}") @@ -291,6 +294,10 @@ class Language(str, Enum): from solidlsp.language_servers.julia_server import JuliaLanguageServer return JuliaLanguageServer + case self.HASKELL: + from solidlsp.language_servers.haskell_language_server import HaskellLanguageServer + + return HaskellLanguageServer case _: raise ValueError(f"Unhandled language: {self}") diff --git a/test/resources/repos/haskell/test_repo/app/Main.hs b/test/resources/repos/haskell/test_repo/app/Main.hs new file mode 100644 index 00000000..3f8dddb1 --- /dev/null +++ b/test/resources/repos/haskell/test_repo/app/Main.hs @@ -0,0 +1,26 @@ +module Main (main) where + +import Calculator +import Helper + +main :: IO () +main = do + let calc = Calculator "TestCalc" 1 + putStrLn $ "Using " ++ calcName calc ++ " version " ++ show (calcVersion calc) + + -- Test add function (cross-file reference) + let result1 = add 5 3 + putStrLn $ "5 + 3 = " ++ show result1 + + -- Test subtract (uses validateNumber from Helper) + let result2 = Calculator.subtract 10 4 + putStrLn $ "10 - 4 = " ++ show result2 + + -- Test calculate function + case calculate calc "multiply" 6 7 of + Just result -> putStrLn $ "6 * 7 = " ++ show result + Nothing -> putStrLn "Calculation failed" + + -- Test helper functions directly + putStrLn $ "Is 5 positive? " ++ show (isPositive 5) + putStrLn $ "Absolute of -10: " ++ show (absolute (-10)) diff --git a/test/resources/repos/haskell/test_repo/haskell-test-repo.cabal b/test/resources/repos/haskell/test_repo/haskell-test-repo.cabal new file mode 100644 index 00000000..809b2a9e --- /dev/null +++ b/test/resources/repos/haskell/test_repo/haskell-test-repo.cabal @@ -0,0 +1,45 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.38.1. +-- +-- see: https://github.com/sol/hpack + +name: haskell-test-repo +version: 0.1.0.0 +homepage: https://github.com/test/haskell-test-repo#readme +bug-reports: https://github.com/test/haskell-test-repo/issues +author: Test Author +maintainer: test@example.com +license: BSD3 +build-type: Simple + +source-repository head + type: git + location: https://github.com/test/haskell-test-repo + +library + exposed-modules: + Calculator + Helper + other-modules: + Paths_haskell_test_repo + hs-source-dirs: + src + default-extensions: + OverloadedStrings + build-depends: + base >=4.7 && <5 + default-language: Haskell2010 + +executable haskell-test-repo-exe + main-is: Main.hs + other-modules: + Paths_haskell_test_repo + hs-source-dirs: + app + default-extensions: + OverloadedStrings + build-depends: + base >=4.7 && <5 + , haskell-test-repo + default-language: Haskell2010 diff --git a/test/resources/repos/haskell/test_repo/package.yaml b/test/resources/repos/haskell/test_repo/package.yaml new file mode 100644 index 00000000..f489e49b --- /dev/null +++ b/test/resources/repos/haskell/test_repo/package.yaml @@ -0,0 +1,25 @@ +name: haskell-test-repo +version: 0.1.0.0 +github: "test/haskell-test-repo" +license: BSD3 +author: "Test Author" +maintainer: "test@example.com" + +dependencies: + - base >= 4.7 && < 5 + +library: + source-dirs: src + exposed-modules: + - Calculator + - Helper + +executables: + haskell-test-repo-exe: + main: Main.hs + source-dirs: app + dependencies: + - haskell-test-repo + +default-extensions: + - OverloadedStrings diff --git a/test/resources/repos/haskell/test_repo/src/Calculator.hs b/test/resources/repos/haskell/test_repo/src/Calculator.hs new file mode 100644 index 00000000..40c1839b --- /dev/null +++ b/test/resources/repos/haskell/test_repo/src/Calculator.hs @@ -0,0 +1,43 @@ +module Calculator + ( Calculator(..) + , add + , subtract + , multiply + , divide + , calculate + ) where + +import Prelude hiding (subtract) +import Helper (validateNumber) + +-- | A simple calculator data type +data Calculator = Calculator + { calcName :: String + , calcVersion :: Int + } deriving (Show, Eq) + +-- | Add two numbers +add :: Int -> Int -> Int +add x y = validateNumber x + validateNumber y + +-- | Subtract two numbers +subtract :: Int -> Int -> Int +subtract x y = validateNumber x - validateNumber y + +-- | Multiply two numbers +multiply :: Int -> Int -> Int +multiply x y = x * y + +-- | Divide two numbers (returns Maybe to handle division by zero) +divide :: Int -> Int -> Maybe Int +divide _ 0 = Nothing +divide x y = Just (x `div` y) + +-- | Perform a calculation based on operator +calculate :: Calculator -> String -> Int -> Int -> Maybe Int +calculate calc op x y = case op of + "add" -> Just (add x y) + "subtract" -> Just (subtract x y) + "multiply" -> Just (multiply x y) + "divide" -> divide x y + _ -> Nothing diff --git a/test/resources/repos/haskell/test_repo/src/Helper.hs b/test/resources/repos/haskell/test_repo/src/Helper.hs new file mode 100644 index 00000000..9fa04c38 --- /dev/null +++ b/test/resources/repos/haskell/test_repo/src/Helper.hs @@ -0,0 +1,22 @@ +module Helper + ( validateNumber + , isPositive + , isNegative + , absolute + ) where + +-- | Validate that a number is not zero (for demonstration) +validateNumber :: Int -> Int +validateNumber x = if x == 0 then error "Zero not allowed" else x + +-- | Check if a number is positive +isPositive :: Int -> Bool +isPositive x = x > 0 + +-- | Check if a number is negative +isNegative :: Int -> Bool +isNegative x = x < 0 + +-- | Get absolute value +absolute :: Int -> Int +absolute x = if isNegative x then negate x else x diff --git a/test/resources/repos/haskell/test_repo/stack.yaml b/test/resources/repos/haskell/test_repo/stack.yaml new file mode 100644 index 00000000..1ecc3671 --- /dev/null +++ b/test/resources/repos/haskell/test_repo/stack.yaml @@ -0,0 +1,5 @@ +resolver: ghc-9.8.4 +system-ghc: true +install-ghc: false +packages: + - . diff --git a/test/solidlsp/haskell/__init__.py b/test/solidlsp/haskell/__init__.py new file mode 100644 index 00000000..760bf8f8 --- /dev/null +++ b/test/solidlsp/haskell/__init__.py @@ -0,0 +1 @@ +# Haskell language server tests diff --git a/test/solidlsp/haskell/test_haskell_basic.py b/test/solidlsp/haskell/test_haskell_basic.py new file mode 100644 index 00000000..062cc9ea --- /dev/null +++ b/test/solidlsp/haskell/test_haskell_basic.py @@ -0,0 +1,219 @@ +""" +Rigorous tests for Haskell Language Server integration with Serena. + +Tests prove that Serena's symbol tools can: +1. Discover all expected symbols with precise matching +2. Track cross-file references accurately +3. Identify data type structures and record fields +4. Navigate between definitions and usages + +Test Repository Structure: +- src/Calculator.hs: Calculator data type, arithmetic functions (add, subtract, multiply, divide, calculate) +- src/Helper.hs: Helper functions (validateNumber, isPositive, isNegative, absolute) +- app/Main.hs: Main entry point using Calculator and Helper modules +""" + +import sys + +import pytest + +from solidlsp.ls import SolidLanguageServer +from solidlsp.ls_config import Language + + +@pytest.mark.haskell +@pytest.mark.skipif(sys.platform == "win32", reason="HLS not installed on Windows CI") +class TestHaskellLanguageServer: + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_calculator_module_symbols(self, language_server: SolidLanguageServer): + """ + Test precise symbol discovery in Calculator.hs. + + Verifies that Serena can identify: + - Data type definition (Calculator with record fields) + - All exported functions with correct names + - Module structure + """ + all_symbols, _ = language_server.request_document_symbols("src/Calculator.hs") + symbol_names = {s["name"] for s in all_symbols} + + # Verify exact set of expected top-level symbols + expected_symbols = { + "Calculator", # Data type + "add", # Function: Int -> Int -> Int + "subtract", # Function: Int -> Int -> Int + "multiply", # Function: Int -> Int -> Int + "divide", # Function: Int -> Int -> Maybe Int + "calculate", # Function: Calculator -> String -> Int -> Int -> Maybe Int + } + + # Verify all expected symbols are present + missing = expected_symbols - symbol_names + assert not missing, f"Missing expected symbols in Calculator.hs: {missing}" + + # Verify Calculator data type exists + calculator_symbol = next((s for s in all_symbols if s["name"] == "Calculator"), None) + assert calculator_symbol is not None, "Calculator data type not found" + + # The Calculator should be identified as a data type + # HLS may use different SymbolKind values (1=File, 5=Class, 23=Struct) + assert calculator_symbol["kind"] in [ + 1, + 5, + 23, + ], f"Calculator should be a data type (kind 1, 5, or 23), got kind {calculator_symbol['kind']}" + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_helper_module_symbols(self, language_server: SolidLanguageServer): + """ + Test precise symbol discovery in Helper.hs. + + Verifies Serena identifies all helper functions that are imported + and used by Calculator module. + """ + all_symbols, _ = language_server.request_document_symbols("src/Helper.hs") + symbol_names = {s["name"] for s in all_symbols} + + # Verify expected helper functions (module name may also appear) + expected_symbols = { + "validateNumber", # Function used by Calculator.add and Calculator.subtract + "isPositive", # Predicate function + "isNegative", # Predicate function used by absolute + "absolute", # Function that uses isNegative + } + + # All expected symbols should be present (module name is optional) + missing = expected_symbols - symbol_names + assert not missing, f"Missing expected symbols in Helper.hs: {missing}" + + # Verify no unexpected symbols beyond the module name + extra = symbol_names - expected_symbols - {"Helper"} + assert not extra, f"Unexpected symbols in Helper.hs: {extra}" + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_main_module_imports(self, language_server: SolidLanguageServer): + """ + Test that Main.hs properly references both Calculator and Helper modules. + + Verifies Serena can identify cross-module dependencies. + """ + all_symbols, _ = language_server.request_document_symbols("app/Main.hs") + symbol_names = {s["name"] for s in all_symbols} + + # Main.hs should have the main function + assert "main" in symbol_names, "Main.hs should contain 'main' function" + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_cross_file_references_validateNumber(self, language_server: SolidLanguageServer): + """ + Test cross-file reference tracking for validateNumber function. + + validateNumber is defined in Helper.hs:9 and used in: + - Calculator.hs:21 (in add function) + - Calculator.hs:25 (in subtract function) + + This proves Serena can track function usage across module boundaries. + """ + # Get references to validateNumber (defined at line 9, 0-indexed = line 8) + references = language_server.request_references("src/Helper.hs", line=8, column=0) + + # Should find at least: definition in Helper.hs + 2 usages in Calculator.hs + assert len(references) >= 2, f"Expected at least 2 references to validateNumber (used in add and subtract), got {len(references)}" + + # Verify we have references in Calculator.hs + reference_paths = [ref["relativePath"] for ref in references] + calculator_refs = [path for path in reference_paths if "Calculator.hs" in path] + + assert len(calculator_refs) >= 2, ( + f"Expected at least 2 references in Calculator.hs (add and subtract functions), " + f"got {len(calculator_refs)} references in Calculator.hs" + ) + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_within_file_references_isNegative(self, language_server: SolidLanguageServer): + """ + Test within-file reference tracking for isNegative function. + + isNegative is defined in Helper.hs:17 and used in Helper.hs:22 (absolute function). + This proves Serena can track intra-module function calls. + """ + # isNegative defined at line 17 (0-indexed = line 16) + references = language_server.request_references("src/Helper.hs", line=16, column=0) + + # Should find: definition + usage in absolute function + assert len(references) >= 1, f"Expected at least 1 reference to isNegative (used in absolute), got {len(references)}" + + # All references should be in Helper.hs + reference_paths = [ref["relativePath"] for ref in references] + assert all( + "Helper.hs" in path for path in reference_paths + ), f"All isNegative references should be in Helper.hs, got: {reference_paths}" + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_function_references_from_main(self, language_server: SolidLanguageServer): + """ + Test that functions used in Main.hs can be traced back to their definitions. + + Main.hs:12 calls 'add' from Calculator module. + Main.hs:25 calls 'isPositive' from Helper module. + Main.hs:26 calls 'absolute' from Helper module. + + This proves Serena can track cross-module function calls from executable code. + """ + # Test 'add' function references (defined in Calculator.hs:20, 0-indexed = line 19) + add_refs = language_server.request_references("src/Calculator.hs", line=19, column=0) + + # Should find references in Main.hs and possibly Calculator.hs (calculate function uses it) + assert len(add_refs) >= 1, f"Expected at least 1 reference to 'add', got {len(add_refs)}" + + add_ref_paths = [ref["relativePath"] for ref in add_refs] + # Should have at least one reference in Main.hs or Calculator.hs + assert any( + "Main.hs" in path or "Calculator.hs" in path for path in add_ref_paths + ), f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}" + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer): + """ + Test that multiply function usage is tracked within Calculator module. + + multiply is defined in Calculator.hs:28 and used in: + - Calculator.hs:41 (in calculate function via pattern matching) + - Main.hs:20 (via calculate call with "multiply" operator) + + This proves Serena can track function references even when called indirectly. + """ + # multiply defined at line 28 (0-indexed = line 27) + multiply_refs = language_server.request_references("src/Calculator.hs", line=27, column=0) + + # Should find at least the usage in calculate function + assert len(multiply_refs) >= 1, f"Expected at least 1 reference to 'multiply', got {len(multiply_refs)}" + + # Should have reference in Calculator.hs (calculate function) + multiply_ref_paths = [ref["relativePath"] for ref in multiply_refs] + assert any( + "Calculator.hs" in path for path in multiply_ref_paths + ), f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}" + + @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) + def test_data_type_constructor_references(self, language_server: SolidLanguageServer): + """ + Test that Calculator data type constructor usage is tracked. + + Calculator is defined in Calculator.hs:14 and used in: + - Main.hs:8 (constructor call: Calculator "TestCalc" 1) + - Calculator.hs:37 (type signature for calculate function) + + This proves Serena can track data type constructor references. + """ + # Calculator data type defined at line 14 (0-indexed = line 13) + calculator_refs = language_server.request_references("src/Calculator.hs", line=13, column=5) + + # Should find usage in Main.hs + assert len(calculator_refs) >= 1, f"Expected at least 1 reference to Calculator constructor, got {len(calculator_refs)}" + + # Should have at least one reference in Main.hs or Calculator.hs + calc_ref_paths = [ref["relativePath"] for ref in calculator_refs] + assert any( + "Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths + ), f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}" From 4bf2cbe8189e0c2e7a0d871c5b259f678a43e44c Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Mon, 27 Oct 2025 00:18:25 +0100 Subject: [PATCH 026/253] Readme, changelog (mention Julia support) [ci skip] --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d323eb..154b3939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Status of the `main` branch. Changes prior to the next official version change w * Added `RenameSymbolTool` for renaming symbols across the codebase (if LS supports this operation). * Language support: + * **Add support for Julia** via LanguageServer.jl * **Add support for Haskell** via Haskell Language Server (HLS) with automatic discovery via ghcup, stack, or system PATH; supports both Stack and Cabal projects * **Add support for Scala** via Metals language server (requires some [manual setup](docs/scala_setup_guide_for_serena.md)) * **Add support for Elm** via @elm-tooling/elm-language-server (automatically downloads if not installed; requires Elm compiler) diff --git a/README.md b/README.md index f6a1faea..e88f5592 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ With Serena, we provide direct, out-of-the-box support for: * Erlang (requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls), experimental, might be slow or hang) * Perl (requires installation of Perl::LanguageServer) * Haskell (automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects) + * Julia * AL * Markdown (must be explicitly specified via `--language markdown` when generating project config, primarily useful for documentation-heavy projects) From 45407f7a2a525a3dda294cbc6a5aba0db0d3bb5d Mon Sep 17 00:00:00 2001 From: David Markey Date: Mon, 27 Oct 2025 14:57:56 +0000 Subject: [PATCH 027/253] Update agno API to the newest version, switch to AgentOS (#702) --- pyproject.toml | 4 ++-- scripts/agno_agent.py | 23 ++++++++++++++++------- src/serena/agno.py | 22 ++++++++++------------ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dde69b81..e4df3a86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ dev = [ "syrupy>=4.9.1", "types-requests>=2.32.4.20241230", ] -agno = ["agno>=1.2.6", "sqlalchemy>=2.0.40"] +agno = ["agno>=2.2.1", "sqlalchemy>=2.0.40"] google = ["google-genai>=1.8.0"] [project.urls] @@ -289,4 +289,4 @@ markers = [ skip = '.git*,*.svg,*.lock,*.min.*' check-hidden = true # ignore-regex = '' -# ignore-words-list = '' \ No newline at end of file +# ignore-words-list = '' diff --git a/scripts/agno_agent.py b/scripts/agno_agent.py index 5c8e50a4..53473fd2 100644 --- a/scripts/agno_agent.py +++ b/scripts/agno_agent.py @@ -1,7 +1,6 @@ from agno.models.anthropic.claude import Claude from agno.models.google.gemini import Gemini -from agno.playground.playground import Playground -from agno.playground.serve import serve_playground_app +from agno.os import AgentOS from sensai.util import logging from sensai.util.helper import mark_used @@ -9,16 +8,26 @@ from serena.agno import SerenaAgnoAgentProvider mark_used(Gemini, Claude) -# initialize logging (Note: since this module is reimported by serve_playground_app and the logging configuration -# is extended by SerenaAgentProvider, we must handle this here conditionally) +# initialize logging if __name__ == "__main__": logging.configure(level=logging.INFO) # Define the model to use (see Agno documentation for supported models; these are just examples) # model = Claude(id="claude-3-7-sonnet-20250219") -model = Gemini(id="gemini-2.5-pro-exp-03-25") +model = Gemini(id="gemini-2.5-pro") -app = Playground(agents=[SerenaAgnoAgentProvider.get_agent(model)]).get_app() +# Create the Serena agent using the existing provider +serena_agent = SerenaAgnoAgentProvider.get_agent(model) + +# Create AgentOS app with the Serena agent +agent_os = AgentOS( + description="Serena coding assistant powered by AgentOS", + id="serena-agentos", + agents=[serena_agent], +) + +app = agent_os.get_app() if __name__ == "__main__": - serve_playground_app("agno_agent:app", reload=False, log_config=None) + # Start the AgentOS server + agent_os.serve(app="agno_agent:app", reload=False) diff --git a/src/serena/agno.py b/src/serena/agno.py index 07ab7371..253c4a49 100644 --- a/src/serena/agno.py +++ b/src/serena/agno.py @@ -6,9 +6,9 @@ from pathlib import Path from typing import Any from agno.agent import Agent -from agno.memory import AgentMemory +from agno.db.sqlite import SqliteDb +from agno.memory import MemoryManager from agno.models.base import Model -from agno.storage.sqlite import SqliteStorage from agno.tools.function import Function from agno.tools.toolkit import Toolkit from dotenv import load_dotenv @@ -111,10 +111,9 @@ class SerenaAgnoAgentProvider: raise # Even though we don't want to keep history between sessions, - # for agno-ui to work as a conversation, we use a persistent storage on disk. - # This storage should be deleted between sessions. + # for agno-ui to work as a conversation, we use a persistent database on disk. + # This database should be deleted between sessions. # Note that this might collide with custom options for the agent, like adding vector-search based tools. - # See here for an explanation: https://www.reddit.com/r/agno/comments/1jk6qea/regarding_the_built_in_memory/ sql_db_path = (Path("temp") / "agno_agent_storage.db").absolute() sql_db_path.parent.mkdir(exist_ok=True) # delete the db file if it exists @@ -125,19 +124,18 @@ class SerenaAgnoAgentProvider: agno_agent = Agent( name="Serena", model=model, - # See explanation above on why storage is needed - storage=SqliteStorage(table_name="serena_agent_sessions", db_file=str(sql_db_path)), + # See explanation above on why database is needed + db=SqliteDb(db_file=str(sql_db_path)), description="A fully-featured coding assistant", tools=[SerenaAgnoToolkit(serena_agent)], - # The tool calls will be shown in the UI anyway since whether to show them is configurable per tool + # Tool calls will be shown in the UI since that's configurable per tool # To see detailed logs, you should use the serena logger (configure it in the project file path) - show_tool_calls=False, markdown=True, system_message=serena_agent.create_system_prompt(), telemetry=False, - memory=AgentMemory(), - add_history_to_messages=True, - num_history_responses=100, # you might want to adjust this (expense vs. history awareness) + memory_manager=MemoryManager(), + add_history_to_context=True, + num_history_runs=100, # you might want to adjust this (expense vs. history awareness) ) cls._agent = agno_agent log.info(f"Agent instantiated: {agno_agent}") From a8f6ad92cc67270671e1d878b30d9daccadc4015 Mon Sep 17 00:00:00 2001 From: Suvarchal Kumar Cheedela <15322959+suvarchal@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:49:32 +0100 Subject: [PATCH 028/253] Add Fortran language support via fortls (#698) --- CHANGELOG.md | 2 + README.md | 1 + pyproject.toml | 2 + .../fortran_language_server.py | 311 +++++++ src/solidlsp/ls_config.py | 9 + .../repos/fortran/test_repo/main.f90 | 16 + .../fortran/test_repo/modules/geometry.f90 | 46 + .../fortran/test_repo/modules/math_utils.f90 | 22 + test/solidlsp/fortran/__init__.py | 1 + test/solidlsp/fortran/test_fortran_basic.py | 274 ++++++ uv.lock | 832 +++++++++--------- 11 files changed, 1112 insertions(+), 404 deletions(-) create mode 100644 src/solidlsp/language_servers/fortran_language_server.py create mode 100644 test/resources/repos/fortran/test_repo/main.f90 create mode 100644 test/resources/repos/fortran/test_repo/modules/geometry.f90 create mode 100644 test/resources/repos/fortran/test_repo/modules/math_utils.f90 create mode 100644 test/solidlsp/fortran/__init__.py create mode 100644 test/solidlsp/fortran/test_fortran_basic.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 154b3939..13700677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Status of the `main` branch. Changes prior to the next official version change w * Added `RenameSymbolTool` for renaming symbols across the codebase (if LS supports this operation). * Language support: + + * **Add support for Fortran** via fortls language server (requires `pip install fortls`) * **Add support for Julia** via LanguageServer.jl * **Add support for Haskell** via Haskell Language Server (HLS) with automatic discovery via ghcup, stack, or system PATH; supports both Stack and Cabal projects * **Add support for Scala** via Metals language server (requires some [manual setup](docs/scala_setup_guide_for_serena.md)) diff --git a/README.md b/README.md index e88f5592..444ea91b 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ With Serena, we provide direct, out-of-the-box support for: * Scala (requires some [manual setup](docs/scala_setup_guide_for_serena.md); uses Metals LSP) * Erlang (requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls), experimental, might be slow or hang) * Perl (requires installation of Perl::LanguageServer) + * Fortran (requires installation of fortls: `pip install fortls`) * Haskell (automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects) * Julia * AL diff --git a/pyproject.toml b/pyproject.toml index e4df3a86..3d852854 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ dependencies = [ "requests>=2.32.3,<3", "pyright>=1.1.396,<2", + "fortls>=3.2.2", "overrides>=7.7.0,<8", "python-dotenv>=1.0.0, <2", "mcp==1.12.3", @@ -281,6 +282,7 @@ markers = [ "rego: language server running for Rego", "markdown: language server running for Markdown", "julia: Julia language server tests", + "fortran: language server running for Fortran", "haskell: Haskell language server tests", ] diff --git a/src/solidlsp/language_servers/fortran_language_server.py b/src/solidlsp/language_servers/fortran_language_server.py new file mode 100644 index 00000000..a77135a6 --- /dev/null +++ b/src/solidlsp/language_servers/fortran_language_server.py @@ -0,0 +1,311 @@ +""" +Fortran Language Server implementation using fortls. +""" + +import logging +import os +import pathlib +import re +import shutil +import threading + +from overrides import override + +from solidlsp import ls_types +from solidlsp.ls import SolidLanguageServer +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.settings import SolidLSPSettings + + +class FortranLanguageServer(SolidLanguageServer): + """Fortran Language Server implementation using fortls.""" + + @override + def _get_wait_time_for_cross_file_referencing(self) -> float: + return 3.0 # fortls needs time for workspace indexing + + @override + def is_ignored_dirname(self, dirname: str) -> bool: + # For Fortran projects, ignore common build directories + return super().is_ignored_dirname(dirname) or dirname in [ + "build", + "Build", + "BUILD", + "bin", + "lib", + "mod", # Module files directory + "obj", # Object files directory + ".cmake", + "CMakeFiles", + ] + + def _fix_fortls_selection_range( + self, symbol: ls_types.UnifiedSymbolInformation, file_content: str + ) -> ls_types.UnifiedSymbolInformation: + """ + Fix fortls's incorrect selectionRange that points to line start instead of identifier name. + + fortls bug: selectionRange.start.character is 0 (line start) but should point to the + function/subroutine/module/program name position. This breaks MCP server features that + rely on the exact identifier position for finding references. + + Args: + symbol: The symbol with potentially incorrect selectionRange + file_content: Full file content to parse the line + + Returns: + Symbol with corrected selectionRange pointing to the identifier name + + """ + if "selectionRange" not in symbol: + return symbol + + sel_range = symbol["selectionRange"] + start_line = sel_range["start"]["line"] + start_char = sel_range["start"]["character"] + + # Split file content into lines + lines = file_content.split("\n") + if start_line >= len(lines): + return symbol + + line = lines[start_line] + + # Fortran keywords that define named constructs + # Match patterns: + # Standard keywords: + # " function add_numbers(a, b) result(sum)" -> keyword="function", name="add_numbers" + # "subroutine print_result(value)" -> keyword="subroutine", name="print_result" + # "module math_utils" -> keyword="module", name="math_utils" + # "program test_program" -> keyword="program", name="test_program" + # "interface distance" -> keyword="interface", name="distance" + # + # Type definitions (can have :: syntax): + # "type point" -> keyword="type", name="point" + # "type :: point" -> keyword="type", name="point" + # "type, extends(base) :: derived" -> keyword="type", name="derived" + # + # Submodules (have parent module in parentheses): + # "submodule (parent_mod) child_mod" -> keyword="submodule", name="child_mod" + + # Try type pattern first (has complex syntax with optional comma and ::) + type_pattern = r"^\s*type\s*(?:,.*?)?\s*(?:::)?\s*([a-zA-Z_]\w*)" + match = re.match(type_pattern, line, re.IGNORECASE) + + if match: + # For type pattern, identifier is in group 1 + identifier_name = match.group(1) + identifier_start = match.start(1) + else: + # Try standard keywords pattern + standard_pattern = r"^\s*(function|subroutine|module|program|interface)\s+([a-zA-Z_]\w*)" + match = re.match(standard_pattern, line, re.IGNORECASE) + + if not match: + # Try submodule pattern + submodule_pattern = r"^\s*submodule\s*\([^)]+\)\s+([a-zA-Z_]\w*)" + match = re.match(submodule_pattern, line, re.IGNORECASE) + + if match: + identifier_name = match.group(1) + identifier_start = match.start(1) + else: + identifier_name = match.group(2) + identifier_start = match.start(2) + + if match: + # Create corrected selectionRange + new_sel_range = { + "start": {"line": start_line, "character": identifier_start}, + "end": {"line": start_line, "character": identifier_start + len(identifier_name)}, + } + + # Create modified symbol with corrected selectionRange + corrected_symbol = symbol.copy() + corrected_symbol["selectionRange"] = new_sel_range + + self.logger.log( + f"Fixed fortls selectionRange for {identifier_name}: char {start_char} -> {identifier_start}", + logging.DEBUG, + ) + + return corrected_symbol + + # If no match, return symbol unchanged (e.g., for variables, which don't have this pattern) + return symbol + + @override + def request_document_symbols( + self, relative_file_path: str, include_body: bool = False + ) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]: + """ + Override to fix fortls's incorrect selectionRange bug. + + fortls returns selectionRange pointing to line start (character 0) instead of the + identifier name position. This breaks MCP server features that rely on exact positions. + + This override: + 1. Gets symbols from fortls via parent implementation + 2. Parses each symbol's line to find the correct identifier position + 3. Fixes selectionRange for all symbols recursively + 4. Returns corrected symbols + + """ + # Get symbols from fortls (with incorrect selectionRange) + all_symbols, root_symbols = super().request_document_symbols(relative_file_path, include_body) + + # Get file content for parsing + with self.open_file(relative_file_path) as file_data: + file_content = file_data.contents + + # Fix selectionRange recursively for all symbols + def fix_symbol_and_children(symbol: ls_types.UnifiedSymbolInformation) -> ls_types.UnifiedSymbolInformation: + # Fix this symbol's selectionRange + fixed = self._fix_fortls_selection_range(symbol, file_content) + + # Fix children recursively + if fixed.get("children"): + fixed["children"] = [fix_symbol_and_children(child) for child in fixed["children"]] + + return fixed + + # Apply fix to all symbols + fixed_all_symbols = [fix_symbol_and_children(sym) for sym in all_symbols] + fixed_root_symbols = [fix_symbol_and_children(sym) for sym in root_symbols] + + return fixed_all_symbols, fixed_root_symbols + + @staticmethod + def _check_fortls_installation(): + """Check if fortls is available.""" + fortls_path = shutil.which("fortls") + if fortls_path is None: + raise RuntimeError("fortls is not installed or not in PATH.\nInstall it with: pip install fortls") + return fortls_path + + def __init__( + self, + config: LanguageServerConfig, + logger: LanguageServerLogger, + repository_root_path: str, + solidlsp_settings: SolidLSPSettings, + ): + # Check fortls installation + fortls_path = self._check_fortls_installation() + + # Command to start fortls language server + # fortls uses stdio for LSP communication by default + fortls_cmd = f"{fortls_path}" + + super().__init__( + config, + logger, + repository_root_path, + ProcessLaunchInfo(cmd=fortls_cmd, cwd=repository_root_path), + "fortran", # Language ID for LSP + solidlsp_settings, + ) + self.server_ready = threading.Event() + + @staticmethod + def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: + """Initialize params for Fortran Language Server.""" + root_uri = pathlib.Path(repository_absolute_path).as_uri() + initialize_params = { + "locale": "en", + "capabilities": { + "textDocument": { + "synchronization": {"didSave": True, "dynamicRegistration": True}, + "completion": { + "dynamicRegistration": True, + "completionItem": { + "snippetSupport": True, + "commitCharactersSupport": True, + "documentationFormat": ["markdown", "plaintext"], + "deprecatedSupport": True, + "preselectSupport": True, + }, + }, + "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, + "definition": {"dynamicRegistration": True}, + "references": {"dynamicRegistration": True}, + "documentSymbol": { + "dynamicRegistration": True, + "hierarchicalDocumentSymbolSupport": True, + "symbolKind": {"valueSet": list(range(1, 27))}, + }, + "formatting": {"dynamicRegistration": True}, + "rangeFormatting": {"dynamicRegistration": True}, + "codeAction": {"dynamicRegistration": True}, + }, + "workspace": { + "workspaceFolders": True, + "didChangeConfiguration": {"dynamicRegistration": True}, + "symbol": { + "dynamicRegistration": True, + "symbolKind": {"valueSet": list(range(1, 27))}, + }, + }, + }, + "processId": os.getpid(), + "rootPath": repository_absolute_path, + "rootUri": root_uri, + "workspaceFolders": [ + { + "uri": root_uri, + "name": os.path.basename(repository_absolute_path), + } + ], + } + return initialize_params + + def _start_server(self): + """Start Fortran Language Server process.""" + + def window_log_message(msg): + self.logger.log(f"Fortran LSP: window/logMessage: {msg}", logging.INFO) + + def do_nothing(params): + return + + def register_capability_handler(params): + return + + # Register LSP message handlers + self.server.on_request("client/registerCapability", register_capability_handler) + self.server.on_notification("window/logMessage", window_log_message) + self.server.on_notification("$/progress", do_nothing) + self.server.on_notification("textDocument/publishDiagnostics", do_nothing) + + self.logger.log("Starting Fortran Language Server (fortls) process", logging.INFO) + self.server.start() + + initialize_params = self._get_initialize_params(self.repository_root_path) + self.logger.log( + "Sending initialize request to Fortran Language Server", + logging.INFO, + ) + + init_response = self.server.send.initialize(initialize_params) + + # Verify server capabilities + capabilities = init_response.get("capabilities", {}) + assert "textDocumentSync" in capabilities + if "completionProvider" in capabilities: + self.logger.log("Fortran LSP completion provider available", logging.INFO) + if "definitionProvider" in capabilities: + self.logger.log("Fortran LSP definition provider available", logging.INFO) + if "referencesProvider" in capabilities: + self.logger.log("Fortran LSP references provider available", logging.INFO) + if "documentSymbolProvider" in capabilities: + self.logger.log("Fortran LSP document symbol provider available", logging.INFO) + + self.server.notify.initialized({}) + self.completions_available.set() + + # Fortran Language Server is ready after initialization + self.server_ready.set() + self.logger.log("Fortran Language Server initialization complete", logging.INFO) diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index 741ab43e..580b0c95 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -58,6 +58,7 @@ class Language(str, Enum): REGO = "rego" SCALA = "scala" JULIA = "julia" + FORTRAN = "fortran" HASKELL = "haskell" # Experimental or deprecated Language Servers TYPESCRIPT_VTS = "typescript_vts" @@ -159,6 +160,10 @@ class Language(str, Enum): return FilenameMatcher("*.scala", "*.sbt") case self.JULIA: return FilenameMatcher("*.jl") + case self.FORTRAN: + return FilenameMatcher( + "*.f90", "*.F90", "*.f95", "*.F95", "*.f03", "*.F03", "*.f08", "*.F08", "*.f", "*.F", "*.for", "*.FOR", "*.fpp", "*.FPP" + ) case self.HASKELL: return FilenameMatcher("*.hs", "*.lhs") case _: @@ -294,6 +299,10 @@ class Language(str, Enum): from solidlsp.language_servers.julia_server import JuliaLanguageServer return JuliaLanguageServer + case self.FORTRAN: + from solidlsp.language_servers.fortran_language_server import FortranLanguageServer + + return FortranLanguageServer case self.HASKELL: from solidlsp.language_servers.haskell_language_server import HaskellLanguageServer diff --git a/test/resources/repos/fortran/test_repo/main.f90 b/test/resources/repos/fortran/test_repo/main.f90 new file mode 100644 index 00000000..6ca2095b --- /dev/null +++ b/test/resources/repos/fortran/test_repo/main.f90 @@ -0,0 +1,16 @@ +program test_program + use math_utils + implicit none + real :: result + + ! Test addition + result = add_numbers(5.0, 3.0) + call print_result(result) + + ! Test multiplication + result = multiply_numbers(4.0, 2.0) + call print_result(result) + + print *, "All tests completed" +end program test_program + diff --git a/test/resources/repos/fortran/test_repo/modules/geometry.f90 b/test/resources/repos/fortran/test_repo/modules/geometry.f90 new file mode 100644 index 00000000..677f9e85 --- /dev/null +++ b/test/resources/repos/fortran/test_repo/modules/geometry.f90 @@ -0,0 +1,46 @@ +module geometry_types + implicit none + + ! Simple type definition + type Point2D + real :: x, y + end type Point2D + + ! Type with double colon syntax + type :: Circle + real :: radius + type(Point2D) :: center + end type Circle + + ! Type with extends (inheritance) + type, extends(Point2D) :: Point3D + real :: z + end type Point3D + + ! Named interface + interface distance + module procedure distance_2d, distance_3d + end interface distance + +contains + + function distance_2d(p1, p2) result(dist) + type(Point2D), intent(in) :: p1, p2 + real :: dist + dist = sqrt((p2%x - p1%x)**2 + (p2%y - p1%y)**2) + end function distance_2d + + function distance_3d(p1, p2) result(dist) + type(Point3D), intent(in) :: p1, p2 + real :: dist + dist = sqrt((p2%x - p1%x)**2 + (p2%y - p1%y)**2 + (p2%z - p1%z)**2) + end function distance_3d + + function circle_area(c) result(area) + type(Circle), intent(in) :: c + real :: area + real, parameter :: pi = 3.14159265359 + area = pi * c%radius**2 + end function circle_area + +end module geometry_types diff --git a/test/resources/repos/fortran/test_repo/modules/math_utils.f90 b/test/resources/repos/fortran/test_repo/modules/math_utils.f90 new file mode 100644 index 00000000..8b5707a8 --- /dev/null +++ b/test/resources/repos/fortran/test_repo/modules/math_utils.f90 @@ -0,0 +1,22 @@ +module math_utils + implicit none + contains + + function add_numbers(a, b) result(sum) + real, intent(in) :: a, b + real :: sum + sum = a + b + end function add_numbers + + function multiply_numbers(x, y) result(product) + real, intent(in) :: x, y + real :: product + product = x * y + end function multiply_numbers + + subroutine print_result(value) + real, intent(in) :: value + print *, "Result is:", value + end subroutine print_result +end module math_utils + diff --git a/test/solidlsp/fortran/__init__.py b/test/solidlsp/fortran/__init__.py new file mode 100644 index 00000000..2ce51422 --- /dev/null +++ b/test/solidlsp/fortran/__init__.py @@ -0,0 +1 @@ +# Fortran language server tests diff --git a/test/solidlsp/fortran/test_fortran_basic.py b/test/solidlsp/fortran/test_fortran_basic.py new file mode 100644 index 00000000..09b3dd20 --- /dev/null +++ b/test/solidlsp/fortran/test_fortran_basic.py @@ -0,0 +1,274 @@ +""" +Basic tests for Fortran language server integration. + +These tests validate some low-level LSP functionality and high-level Serena APIs. +Note: These tests require fortls to be installed: pip install fortls +""" + +import pytest + +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language +from solidlsp.ls_types import SymbolKind +from solidlsp.ls_utils import SymbolUtils + +# Mark all tests in this module as fortran tests +pytestmark = pytest.mark.fortran + + +class TestFortranLanguageServer: + """Test Fortran language server functionality.""" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_find_symbol(self, language_server: SolidLanguageServer) -> None: + """Test finding symbols using request_full_symbol_tree.""" + symbols = language_server.request_full_symbol_tree() + + # Verify program symbol + assert SymbolUtils.symbol_tree_contains_name(symbols, "test_program"), "test_program not found in symbol tree" + + # Verify module symbol + assert SymbolUtils.symbol_tree_contains_name(symbols, "math_utils"), "math_utils module not found in symbol tree" + + # Verify function symbols + assert SymbolUtils.symbol_tree_contains_name(symbols, "add_numbers"), "add_numbers function not found in symbol tree" + assert SymbolUtils.symbol_tree_contains_name(symbols, "multiply_numbers"), "multiply_numbers function not found in symbol tree" + + # Verify subroutine symbol + assert SymbolUtils.symbol_tree_contains_name(symbols, "print_result"), "print_result subroutine not found in symbol tree" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None: + """Test that document symbols can be retrieved from Fortran files.""" + # Test main.f90 - should have a program symbol + main_symbols, _ = language_server.request_document_symbols("main.f90") + program_names = [s.get("name") for s in main_symbols] + assert "test_program" in program_names, f"Program 'test_program' not found in main.f90. Found: {program_names}" + + # Test modules/math_utils.f90 - should have module and function symbols + module_symbols, _ = language_server.request_document_symbols("modules/math_utils.f90") + all_names = [s.get("name") for s in module_symbols] + assert "math_utils" in all_names, f"Module 'math_utils' not found. Found: {all_names}" + assert "add_numbers" in all_names, f"Function 'add_numbers' not found. Found: {all_names}" + assert "multiply_numbers" in all_names, f"Function 'multiply_numbers' not found. Found: {all_names}" + assert "print_result" in all_names, f"Subroutine 'print_result' not found. Found: {all_names}" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_find_references_cross_file(self, language_server: SolidLanguageServer) -> None: + """Test finding references across files using low-level request_references. + + This tests the LSP textDocument/references capability. + """ + file_path = "modules/math_utils.f90" + symbols = language_server.request_document_symbols(file_path) + + # Find the add_numbers function + add_numbers_symbol = None + for sym in symbols[0]: + if sym.get("name") == "add_numbers": + add_numbers_symbol = sym + break + + assert add_numbers_symbol is not None, "Could not find 'add_numbers' function symbol in math_utils.f90" + + # Use selectionRange to query for references + # Note: FortranLanguageServer automatically fixes fortls's incorrect selectionRange + sel_start = add_numbers_symbol["selectionRange"]["start"] + + # Query from the function name position using corrected selectionRange + refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) + + # Should find references (usage in main.f90 + definition in math_utils.f90) + assert len(refs) > 0, "Should find references to add_numbers function" + + # Verify that main.f90 references the function + main_refs = [ref for ref in refs if "main.f90" in ref.get("relativePath", "")] + assert ( + len(main_refs) > 0 + ), f"Expected to find reference in main.f90, but found references in: {[ref.get('relativePath') for ref in refs]}" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_find_definition_cross_file(self, language_server: SolidLanguageServer) -> None: + """Test finding definition across files using request_definition.""" + # In main.f90, line 7 (0-indexed: line 6) contains: result = add_numbers(5.0, 3.0) + # We want to find the definition of add_numbers in modules/math_utils.f90 + main_file = "main.f90" + + # Position on 'add_numbers' usage (approximately column 13) + definition_location_list = language_server.request_definition(main_file, 6, 13) + + if not definition_location_list: + pytest.skip("fortls does not support cross-file go-to-definition for this case") + + assert len(definition_location_list) >= 1, "Should find at least one definition" + definition_location = definition_location_list[0] + + # The definition should be in modules/math_utils.f90 + assert "math_utils.f90" in definition_location.get( + "uri", "" + ), f"Expected definition to be in math_utils.f90, but found in: {definition_location.get('uri')}" + + # Verify the definition is around the correct line (line 4, 0-indexed) + assert ( + definition_location["range"]["start"]["line"] == 4 + ), f"Expected definition at line 4, but found at line {definition_location['range']['start']['line']}" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_request_referencing_symbols(self, language_server: SolidLanguageServer) -> None: + """Test finding symbols that reference a function - Serena's high-level API. + + This tests request_referencing_symbols which returns not just locations but also + the containing symbols that have the references. This is different from + test_find_references_cross_file which only returns locations. + + Note: FortranLanguageServer automatically fixes fortls's incorrect selectionRange. + """ + # Get the add_numbers function symbol from math_utils.f90 + file_path = "modules/math_utils.f90" + symbols, _ = language_server.request_document_symbols(file_path) + + # Find the add_numbers function + add_numbers_symbol = None + for sym in symbols: + if sym.get("name") == "add_numbers": + add_numbers_symbol = sym + break + + assert add_numbers_symbol is not None, "Could not find 'add_numbers' function symbol" + + # Use selectionRange to query for referencing symbols + # FortranLanguageServer automatically corrects fortls's incorrect selectionRange + sel_start = add_numbers_symbol["selectionRange"]["start"] + referencing_symbols = language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"]) + + # Should find referencing symbols (not just locations, but symbols containing the references) + assert len(referencing_symbols) > 0, "Should find referencing symbols when querying from function name position" + + # Extract the symbols from ReferenceInSymbol objects + # This is what makes this test different from test_find_references_cross_file: + # we're testing that we get back SYMBOLS (with name, kind, location) not just locations + ref_symbols = [ref.symbol for ref in referencing_symbols] + + # Verify we got valid symbol structures with all required fields + for symbol in ref_symbols: + assert "name" in symbol, f"Symbol should have a name: {symbol}" + assert "kind" in symbol, f"Symbol should have a kind: {symbol}" + # Each symbol should have location information + assert "location" in symbol, f"Symbol should have location: {symbol}" + + # Note: fortls may not return all cross-file references through request_referencing_symbols + # because it depends on finding containing symbols for each reference. We verify that + # the API works and returns valid symbols with proper structure. + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_request_defining_symbol(self, language_server: SolidLanguageServer) -> None: + """Test finding the defining symbol - Serena's high-level API. + + This is similar to test_find_definition_cross_file but uses the high-level + request_defining_symbol which returns a full symbol with metadata, not just a location. + """ + # In main.f90, line 7 (0-indexed: line 6) contains: result = add_numbers(5.0, 3.0) + # We want to find the definition of add_numbers + main_file = "main.f90" + + # Get the position of add_numbers usage in main.f90 + # Position on 'add_numbers' (approximately column 13) + defining_symbol = language_server.request_defining_symbol(main_file, 6, 13) + + if defining_symbol is None: + pytest.skip("fortls does not support cross-file go-to-definition for this case") + + # Should find the add_numbers function with full symbol information + assert defining_symbol.get("name") == "add_numbers", f"Expected to find 'add_numbers' but got '{defining_symbol.get('name')}'" + + # Check if we have location information + if "location" not in defining_symbol or "relativePath" not in defining_symbol["location"]: + pytest.skip("fortls found the symbol but doesn't provide complete location information") + + # The definition should be in modules/math_utils.f90 + defining_path = defining_symbol["location"]["relativePath"] + assert "math_utils.f90" in defining_path, f"Expected definition to be in math_utils.f90, but found in: {defining_path}" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_request_containing_symbol(self, language_server: SolidLanguageServer) -> None: + """Test finding the containing symbol for a position in the code.""" + # Test finding the containing symbol for a position inside the add_numbers function + file_path = "modules/math_utils.f90" + + # Line 8 (0-indexed: line 7) is inside the add_numbers function: "sum = a + b" + containing_symbol = language_server.request_containing_symbol(file_path, 7, 10, include_body=False) + + if containing_symbol is None: + pytest.skip("fortls does not support request_containing_symbol or couldn't find the containing symbol") + + # Should find the add_numbers function as the containing symbol + assert ( + containing_symbol.get("name") == "add_numbers" + ), f"Expected containing symbol 'add_numbers', got '{containing_symbol.get('name')}'" + + # Verify the symbol kind is Function + assert ( + containing_symbol.get("kind") == SymbolKind.Function.value + ), f"Expected Function kind ({SymbolKind.Function.value}), got {containing_symbol.get('kind')}" + + # Verify location information exists + assert "location" in containing_symbol, "Containing symbol should have location information" + location = containing_symbol["location"] + assert "range" in location, "Location should contain range information" + assert "start" in location["range"] and "end" in location["range"], "Range should have start and end positions" + + @pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) + def test_type_and_interface_symbols(self, language_server: SolidLanguageServer) -> None: + """Test that type definitions and interfaces are properly recognized with corrected selectionRange. + + This verifies that the regex pattern correctly handles: + - Simple type definitions (type Name) + - Type with double colon (type :: Name) + - Type with extends (type, extends(Base) :: Derived) + - Named interfaces + + fortls returns these as SymbolKind.Class (11) for types and SymbolKind.Interface (5) for interfaces. + """ + file_path = "modules/geometry.f90" + symbols, _ = language_server.request_document_symbols(file_path) + + # Find type and interface symbols + type_names = [] + interface_names = [] + for sym in symbols: + if sym.get("kind") == SymbolKind.Class.value: # Type definitions + type_names.append(sym.get("name")) + elif sym.get("kind") == SymbolKind.Interface.value: # Interfaces + interface_names.append(sym.get("name")) + + # Verify type definitions are found + assert "Point2D" in type_names, f"Simple type 'Point2D' not found. Found types: {type_names}" + assert "Circle" in type_names, f"Type with :: syntax 'Circle' not found. Found types: {type_names}" + assert "Point3D" in type_names, f"Type with extends 'Point3D' not found. Found types: {type_names}" + + # Verify interface is found + assert "distance" in interface_names, f"Interface 'distance' not found. Found interfaces: {interface_names}" + + # Verify selectionRange is corrected for a type symbol + point3d_symbol = None + for sym in symbols: + if sym.get("name") == "Point3D": + point3d_symbol = sym + break + + assert point3d_symbol is not None, "Could not find 'Point3D' type symbol" + + # Use corrected selectionRange to find references + # This tests that the fix works for types (not just functions) + sel_start = point3d_symbol["selectionRange"]["start"] + + # Verify selectionRange points to identifier name, not line start + # Line for "type, extends(Point2D) :: Point3D" has Point3D at position > 0 + assert ( + sel_start["character"] > 0 + ), f"selectionRange should point to identifier, not line start. Got character: {sel_start['character']}" + + # Test that we can find references using the corrected position + _refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) + # refs might be empty if Point3D isn't used elsewhere, but the call should not fail + # The important thing is that it doesn't error due to wrong character position diff --git a/uv.lock b/uv.lock index 7174f7fc..d38df339 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 1 +revision = 3 requires-python = "==3.11.*" [[package]] @@ -20,18 +20,18 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/84/d2390249d1b0150971a749df79cf653a631227b748fe10be1ef4f426ed2d/agno-1.7.6.tar.gz", hash = "sha256:8df575f86eac6511996045fb90d281ca61fd19774dccbb82f9dc34bda46effdf", size = 724233 } +sdist = { url = "https://files.pythonhosted.org/packages/b5/84/d2390249d1b0150971a749df79cf653a631227b748fe10be1ef4f426ed2d/agno-1.7.6.tar.gz", hash = "sha256:8df575f86eac6511996045fb90d281ca61fd19774dccbb82f9dc34bda46effdf", size = 724233, upload-time = "2025-07-24T14:36:27.356Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/7e/5f85c92a34f41f8ffb1467768bae50506f734320c478ed24c4237dc342ea/agno-1.7.6-py3-none-any.whl", hash = "sha256:c6350e44747b37048d4ac080ffa5fb0e0ce2b3d5bc55d37120711ae8d31f27c6", size = 962755 }, + { url = "https://files.pythonhosted.org/packages/d9/7e/5f85c92a34f41f8ffb1467768bae50506f734320c478ed24c4237dc342ea/agno-1.7.6-py3-none-any.whl", hash = "sha256:c6350e44747b37048d4ac080ffa5fb0e0ce2b3d5bc55d37120711ae8d31f27c6", size = 962755, upload-time = "2025-07-24T14:36:25.648Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] @@ -47,9 +47,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/52daff015f5a1f24eec891b3041f5f816712fea8b5113dc76638bcbc23d8/anthropic-0.59.0.tar.gz", hash = "sha256:d710d1ef0547ebbb64b03f219e44ba078e83fc83752b96a9b22e9726b523fd8f", size = 425679 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/52daff015f5a1f24eec891b3041f5f816712fea8b5113dc76638bcbc23d8/anthropic-0.59.0.tar.gz", hash = "sha256:d710d1ef0547ebbb64b03f219e44ba078e83fc83752b96a9b22e9726b523fd8f", size = 425679, upload-time = "2025-07-23T16:23:16.901Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/b1/03f680393eac04afd8f2be44ee0e39e033c40faf43dbc1c11764b07a2687/anthropic-0.59.0-py3-none-any.whl", hash = "sha256:cbc8b3dccef66ad6435c4fa1d317e5ebb092399a4b88b33a09dc4bf3944c3183", size = 293057 }, + { url = "https://files.pythonhosted.org/packages/3c/b1/03f680393eac04afd8f2be44ee0e39e033c40faf43dbc1c11764b07a2687/anthropic-0.59.0-py3-none-any.whl", hash = "sha256:cbc8b3dccef66ad6435c4fa1d317e5ebb092399a4b88b33a09dc4bf3944c3183", size = 293057, upload-time = "2025-07-23T16:23:14.934Z" }, ] [[package]] @@ -61,27 +61,27 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] [[package]] name = "asttokens" version = "3.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, ] [[package]] name = "attrs" version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] [[package]] @@ -95,13 +95,13 @@ dependencies = [ { name = "pathspec" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, ] [package.optional-dependencies] @@ -114,49 +114,49 @@ jupyter = [ name = "blinker" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] [[package]] name = "cachetools" version = "5.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, ] [[package]] name = "certifi" version = "2025.7.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722 }, + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] @@ -166,45 +166,45 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "decorator" version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] [[package]] name = "docstring-parser" version = "0.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896 }, + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] [[package]] @@ -215,25 +215,25 @@ dependencies = [ { name = "python-dotenv" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892 }, + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, ] [[package]] name = "execnet" version = "2.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, ] [[package]] name = "executing" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, ] [[package]] @@ -248,9 +248,22 @@ dependencies = [ { name = "markupsafe" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440, upload-time = "2025-05-13T15:01:17.447Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305 }, + { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" }, +] + +[[package]] +name = "fortls" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "json5" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/2b/db1e5cd07fc9e74a2e4fb8f65946f8fd79ef72211001af00982a04d977d5/fortls-3.2.2.tar.gz", hash = "sha256:b43b2b8cbd447ae848c63b8f008c2df96fd48c3a967b33f6ed64b3421496883b", size = 7085222, upload-time = "2025-03-16T18:03:11.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/7e/41a55a49e764243fd72b1ed202a95d9a9193586d8665bdc94e4df92050e4/fortls-3.2.2-py3-none-any.whl", hash = "sha256:b7ae8378ce9741064b8340d3f1fbbbf7ab697285e174a0208fed98485c4edfe3", size = 278359, upload-time = "2025-03-16T18:03:09.969Z" }, ] [[package]] @@ -260,9 +273,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "smmap" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, ] [[package]] @@ -272,9 +285,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168 }, + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, ] [[package]] @@ -286,9 +299,9 @@ dependencies = [ { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029 } +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137 }, + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, ] [[package]] @@ -305,35 +318,35 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/37/6c0ececc3a7a629029b5beed2ceb9f28f73292236eb96272355636769b0d/google_genai-1.27.0.tar.gz", hash = "sha256:15a13ffe7b3938da50b9ab77204664d82122617256f55b5ce403d593848ef635", size = 220099 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/37/6c0ececc3a7a629029b5beed2ceb9f28f73292236eb96272355636769b0d/google_genai-1.27.0.tar.gz", hash = "sha256:15a13ffe7b3938da50b9ab77204664d82122617256f55b5ce403d593848ef635", size = 220099, upload-time = "2025-07-23T22:00:46.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/12/279afe7357af73f9737a3412b6f0bc1482075b896340eb46a2f9cb0fd791/google_genai-1.27.0-py3-none-any.whl", hash = "sha256:afd6b4efaf8ec1d20a6e6657d768b68d998d60007c6e220e9024e23c913c1833", size = 218489 }, + { url = "https://files.pythonhosted.org/packages/5a/12/279afe7357af73f9737a3412b6f0bc1482075b896340eb46a2f9cb0fd791/google_genai-1.27.0-py3-none-any.whl", hash = "sha256:afd6b4efaf8ec1d20a6e6657d768b68d998d60007c6e220e9024e23c913c1833", size = 218489, upload-time = "2025-07-23T22:00:44.879Z" }, ] [[package]] name = "greenlet" version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/2e/d4fcb2978f826358b673f779f78fa8a32ee37df11920dc2bb5589cbeecef/greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", size = 270219 }, - { url = "https://files.pythonhosted.org/packages/16/24/929f853e0202130e4fe163bc1d05a671ce8dcd604f790e14896adac43a52/greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", size = 630383 }, - { url = "https://files.pythonhosted.org/packages/d1/b2/0320715eb61ae70c25ceca2f1d5ae620477d246692d9cc284c13242ec31c/greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", size = 642422 }, - { url = "https://files.pythonhosted.org/packages/bd/49/445fd1a210f4747fedf77615d941444349c6a3a4a1135bba9701337cd966/greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b", size = 638375 }, - { url = "https://files.pythonhosted.org/packages/7e/c8/ca19760cf6eae75fa8dc32b487e963d863b3ee04a7637da77b616703bc37/greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", size = 637627 }, - { url = "https://files.pythonhosted.org/packages/65/89/77acf9e3da38e9bcfca881e43b02ed467c1dedc387021fc4d9bd9928afb8/greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", size = 585502 }, - { url = "https://files.pythonhosted.org/packages/97/c6/ae244d7c95b23b7130136e07a9cc5aadd60d59b5951180dc7dc7e8edaba7/greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", size = 1114498 }, - { url = "https://files.pythonhosted.org/packages/89/5f/b16dec0cbfd3070658e0d744487919740c6d45eb90946f6787689a7efbce/greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba", size = 1139977 }, - { url = "https://files.pythonhosted.org/packages/66/77/d48fb441b5a71125bcac042fc5b1494c806ccb9a1432ecaa421e72157f77/greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34", size = 297017 }, + { url = "https://files.pythonhosted.org/packages/fc/2e/d4fcb2978f826358b673f779f78fa8a32ee37df11920dc2bb5589cbeecef/greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", size = 270219, upload-time = "2025-06-05T16:10:10.414Z" }, + { url = "https://files.pythonhosted.org/packages/16/24/929f853e0202130e4fe163bc1d05a671ce8dcd604f790e14896adac43a52/greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", size = 630383, upload-time = "2025-06-05T16:38:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b2/0320715eb61ae70c25ceca2f1d5ae620477d246692d9cc284c13242ec31c/greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", size = 642422, upload-time = "2025-06-05T16:41:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/bd/49/445fd1a210f4747fedf77615d941444349c6a3a4a1135bba9701337cd966/greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b", size = 638375, upload-time = "2025-06-05T16:48:18.235Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c8/ca19760cf6eae75fa8dc32b487e963d863b3ee04a7637da77b616703bc37/greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", size = 637627, upload-time = "2025-06-05T16:13:02.858Z" }, + { url = "https://files.pythonhosted.org/packages/65/89/77acf9e3da38e9bcfca881e43b02ed467c1dedc387021fc4d9bd9928afb8/greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", size = 585502, upload-time = "2025-06-05T16:12:49.642Z" }, + { url = "https://files.pythonhosted.org/packages/97/c6/ae244d7c95b23b7130136e07a9cc5aadd60d59b5951180dc7dc7e8edaba7/greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", size = 1114498, upload-time = "2025-06-05T16:36:46.598Z" }, + { url = "https://files.pythonhosted.org/packages/89/5f/b16dec0cbfd3070658e0d744487919740c6d45eb90946f6787689a7efbce/greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba", size = 1139977, upload-time = "2025-06-05T16:12:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/66/77/d48fb441b5a71125bcac042fc5b1494c806ccb9a1432ecaa421e72157f77/greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34", size = 297017, upload-time = "2025-06-05T16:25:05.225Z" }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] @@ -344,9 +357,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] @@ -359,36 +372,36 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "httpx-sse" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998 } +sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054 }, + { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -408,9 +421,9 @@ dependencies = [ { name = "traitlets" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338 } +sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338, upload-time = "2025-07-01T11:11:30.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021 }, + { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021, upload-time = "2025-07-01T11:11:27.85Z" }, ] [[package]] @@ -420,18 +433,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, ] [[package]] name = "itsdangerous" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, ] [[package]] @@ -441,9 +454,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, ] [[package]] @@ -453,38 +466,47 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "jiter" version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473 }, - { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971 }, - { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574 }, - { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028 }, - { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083 }, - { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821 }, - { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174 }, - { url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869 }, - { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741 }, - { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527 }, - { url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765 }, - { url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234 }, + { url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473, upload-time = "2025-05-18T19:03:25.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" }, + { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" }, + { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869, upload-time = "2025-05-18T19:03:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765, upload-time = "2025-05-18T19:03:41.271Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234, upload-time = "2025-05-18T19:03:42.918Z" }, ] [[package]] name = "joblib" version = "1.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746 }, + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, +] + +[[package]] +name = "json5" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, ] [[package]] @@ -497,9 +519,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830 } +sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184 }, + { url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" }, ] [[package]] @@ -509,9 +531,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513 } +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437 }, + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] [[package]] @@ -521,27 +543,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, ] [[package]] @@ -551,9 +573,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, ] [[package]] @@ -573,18 +595,18 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/19/9955e2df5384ff5dd25d38f8e88aaf89d2d3d9d39f27e7383eaf0b293836/mcp-1.12.3.tar.gz", hash = "sha256:ab2e05f5e5c13e1dc90a4a9ef23ac500a6121362a564447855ef0ab643a99fed", size = 427203 } +sdist = { url = "https://files.pythonhosted.org/packages/4d/19/9955e2df5384ff5dd25d38f8e88aaf89d2d3d9d39f27e7383eaf0b293836/mcp-1.12.3.tar.gz", hash = "sha256:ab2e05f5e5c13e1dc90a4a9ef23ac500a6121362a564447855ef0ab643a99fed", size = 427203, upload-time = "2025-07-31T18:36:36.795Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8b/0be74e3308a486f1d127f3f6767de5f9f76454c9b4183210c61cc50999b6/mcp-1.12.3-py3-none-any.whl", hash = "sha256:5483345bf39033b858920a5b6348a303acacf45b23936972160ff152107b850e", size = 158810 }, + { url = "https://files.pythonhosted.org/packages/8f/8b/0be74e3308a486f1d127f3f6767de5f9f76454c9b4183210c61cc50999b6/mcp-1.12.3-py3-none-any.whl", hash = "sha256:5483345bf39033b858920a5b6348a303acacf45b23936972160ff152107b850e", size = 158810, upload-time = "2025-07-31T18:36:34.915Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] @@ -596,78 +618,78 @@ dependencies = [ { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114 } +sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150 }, - { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845 }, - { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246 }, - { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106 }, - { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960 }, - { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888 }, - { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195 }, + { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] name = "parso" version = "0.8.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, ] [[package]] name = "pastel" version = "0.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555 } +sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555, upload-time = "2020-09-16T19:21:12.43Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955 }, + { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] @@ -677,27 +699,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] [[package]] name = "platformdirs" version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] @@ -708,9 +730,9 @@ dependencies = [ { name = "pastel" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/ac/311c8a492dc887f0b7a54d0ec3324cb2f9538b7b78ea06e5f7ae1f167e52/poethepoet-0.36.0.tar.gz", hash = "sha256:2217b49cb4e4c64af0b42ff8c4814b17f02e107d38bc461542517348ede25663", size = 66854 } +sdist = { url = "https://files.pythonhosted.org/packages/cf/ac/311c8a492dc887f0b7a54d0ec3324cb2f9538b7b78ea06e5f7ae1f167e52/poethepoet-0.36.0.tar.gz", hash = "sha256:2217b49cb4e4c64af0b42ff8c4814b17f02e107d38bc461542517348ede25663", size = 66854, upload-time = "2025-06-29T19:54:50.444Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/29/dedb3a6b7e17ea723143b834a2da428a7d743c80d5cd4d22ed28b5e8c441/poethepoet-0.36.0-py3-none-any.whl", hash = "sha256:693e3c1eae9f6731d3613c3c0c40f747d3c5c68a375beda42e590a63c5623308", size = 88031 }, + { url = "https://files.pythonhosted.org/packages/03/29/dedb3a6b7e17ea723143b834a2da428a7d743c80d5cd4d22ed28b5e8c441/poethepoet-0.36.0-py3-none-any.whl", hash = "sha256:693e3c1eae9f6731d3613c3c0c40f747d3c5c68a375beda42e590a63c5623308", size = 88031, upload-time = "2025-06-29T19:54:48.884Z" }, ] [[package]] @@ -720,51 +742,51 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, ] [[package]] name = "psutil" version = "7.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, ] [[package]] name = "ptyprocess" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, ] [[package]] name = "pure-eval" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] [[package]] name = "pyasn1" version = "0.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, ] [[package]] @@ -774,9 +796,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] [[package]] @@ -789,9 +811,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] [[package]] @@ -801,31 +823,31 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, ] [[package]] @@ -837,18 +859,18 @@ dependencies = [ { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583 } +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235 }, + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -859,9 +881,9 @@ dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504 }, + { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" }, ] [[package]] @@ -875,9 +897,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] @@ -888,27 +910,27 @@ dependencies = [ { name = "execnet" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069 } +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396 }, + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] [[package]] name = "python-dotenv" version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] name = "python-multipart" version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] [[package]] @@ -916,26 +938,26 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031 }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308 }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930 }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, ] [[package]] @@ -947,32 +969,32 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, ] [[package]] name = "regex" version = "2024.11.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, - { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, - { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, - { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, - { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, - { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, - { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, - { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, - { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, - { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, - { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, - { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, - { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, - { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, - { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, ] [[package]] @@ -985,9 +1007,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] @@ -998,42 +1020,42 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368 }, + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] [[package]] name = "rpds-py" version = "0.26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610 }, - { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032 }, - { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525 }, - { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089 }, - { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255 }, - { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283 }, - { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881 }, - { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822 }, - { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347 }, - { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956 }, - { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363 }, - { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123 }, - { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732 }, - { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917 }, - { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505 }, - { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468 }, - { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680 }, - { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035 }, - { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922 }, - { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822 }, - { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336 }, - { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871 }, - { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439 }, - { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380 }, - { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334 }, + { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610, upload-time = "2025-07-01T15:53:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032, upload-time = "2025-07-01T15:53:59.985Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525, upload-time = "2025-07-01T15:54:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089, upload-time = "2025-07-01T15:54:02.319Z" }, + { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255, upload-time = "2025-07-01T15:54:03.38Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283, upload-time = "2025-07-01T15:54:04.923Z" }, + { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881, upload-time = "2025-07-01T15:54:06.482Z" }, + { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822, upload-time = "2025-07-01T15:54:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347, upload-time = "2025-07-01T15:54:08.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956, upload-time = "2025-07-01T15:54:09.963Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363, upload-time = "2025-07-01T15:54:11.073Z" }, + { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123, upload-time = "2025-07-01T15:54:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732, upload-time = "2025-07-01T15:54:13.434Z" }, + { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917, upload-time = "2025-07-01T15:54:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505, upload-time = "2025-07-01T15:56:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468, upload-time = "2025-07-01T15:56:36.219Z" }, + { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680, upload-time = "2025-07-01T15:56:37.644Z" }, + { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035, upload-time = "2025-07-01T15:56:39.241Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922, upload-time = "2025-07-01T15:56:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822, upload-time = "2025-07-01T15:56:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336, upload-time = "2025-07-01T15:56:44.239Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871, upload-time = "2025-07-01T15:56:46.284Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" }, + { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, ] [[package]] @@ -1043,9 +1065,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 } +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 }, + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, ] [[package]] @@ -1055,51 +1077,51 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/87/6da0df742a4684263261c253f00edd5829e6aca970fff69e75028cccc547/ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7", size = 145511 } +sdist = { url = "https://files.pythonhosted.org/packages/39/87/6da0df742a4684263261c253f00edd5829e6aca970fff69e75028cccc547/ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7", size = 145511, upload-time = "2025-06-09T08:51:09.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/6d/6fe4805235e193aad4aaf979160dd1f3c487c57d48b810c816e6e842171b/ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2", size = 118570 }, + { url = "https://files.pythonhosted.org/packages/af/6d/6fe4805235e193aad4aaf979160dd1f3c487c57d48b810c816e6e842171b/ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2", size = 118570, upload-time = "2025-06-09T08:51:06.348Z" }, ] [[package]] name = "ruamel-yaml-clib" version = "0.2.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 } +sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 }, - { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 }, - { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 }, - { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 }, - { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 }, - { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 }, - { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498 }, - { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 }, - { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 }, + { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, + { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, + { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, + { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, ] [[package]] name = "ruff" version = "0.12.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722 } +sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133 }, - { url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114 }, - { url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873 }, - { url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829 }, - { url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619 }, - { url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894 }, - { url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909 }, - { url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652 }, - { url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451 }, - { url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465 }, - { url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136 }, - { url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644 }, - { url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068 }, - { url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537 }, - { url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575 }, - { url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273 }, - { url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564 }, + { url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" }, + { url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" }, + { url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" }, + { url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" }, + { url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" }, + { url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" }, + { url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" }, ] [[package]] @@ -1109,9 +1131,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/dd/faa2e2de71a03af3def212c70777e794dd54ad5ab87927bb5c29f85f24fc/sensai_utils-1.5.0.tar.gz", hash = "sha256:2ca709a0d5807caf1632d665a455c173987b25276ce61693021672e875f0f17b", size = 61677 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/dd/faa2e2de71a03af3def212c70777e794dd54ad5ab87927bb5c29f85f24fc/sensai_utils-1.5.0.tar.gz", hash = "sha256:2ca709a0d5807caf1632d665a455c173987b25276ce61693021672e875f0f17b", size = 61677, upload-time = "2025-07-28T22:46:10.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/d5/62a0e693230bace8e9a767d6d187a4d9421a7c6ee4b48551f8ff7bd1629a/sensai_utils-1.5.0-py3-none-any.whl", hash = "sha256:c23c51d23d353e7a9b77c70aa8ba7f2d0a8fe4e67ee5bc1ef41c69dba5e4befb", size = 68836 }, + { url = "https://files.pythonhosted.org/packages/ad/d5/62a0e693230bace8e9a767d6d187a4d9421a7c6ee4b48551f8ff7bd1629a/sensai_utils-1.5.0-py3-none-any.whl", hash = "sha256:c23c51d23d353e7a9b77c70aa8ba7f2d0a8fe4e67ee5bc1ef41c69dba5e4befb", size = 68836, upload-time = "2025-07-28T22:46:08.929Z" }, ] [[package]] @@ -1123,6 +1145,7 @@ dependencies = [ { name = "docstring-parser" }, { name = "dotenv" }, { name = "flask" }, + { name = "fortls" }, { name = "jinja2" }, { name = "joblib" }, { name = "mcp" }, @@ -1171,6 +1194,7 @@ requires-dist = [ { name = "docstring-parser", specifier = ">=0.16" }, { name = "dotenv", specifier = ">=0.9.9" }, { name = "flask", specifier = ">=3.0.0" }, + { name = "fortls", specifier = ">=3.2.2" }, { name = "google-genai", marker = "extra == 'google'", specifier = ">=1.8.0" }, { name = "jinja2", specifier = ">=3.1.6" }, { name = "jinja2", marker = "extra == 'dev'" }, @@ -1206,27 +1230,27 @@ provides-extras = ["dev", "agno", "google"] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] [[package]] name = "smmap" version = "5.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] @@ -1237,17 +1261,17 @@ dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424 } +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/4e/b00e3ffae32b74b5180e15d2ab4040531ee1bef4c19755fe7926622dc958/sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", size = 2121232 }, - { url = "https://files.pythonhosted.org/packages/ef/30/6547ebb10875302074a37e1970a5dce7985240665778cfdee2323709f749/sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", size = 2110897 }, - { url = "https://files.pythonhosted.org/packages/9e/21/59df2b41b0f6c62da55cd64798232d7349a9378befa7f1bb18cf1dfd510a/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", size = 3273313 }, - { url = "https://files.pythonhosted.org/packages/62/e4/b9a7a0e5c6f79d49bcd6efb6e90d7536dc604dab64582a9dec220dab54b6/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", size = 3273807 }, - { url = "https://files.pythonhosted.org/packages/39/d8/79f2427251b44ddee18676c04eab038d043cff0e764d2d8bb08261d6135d/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", size = 3209632 }, - { url = "https://files.pythonhosted.org/packages/d4/16/730a82dda30765f63e0454918c982fb7193f6b398b31d63c7c3bd3652ae5/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", size = 3233642 }, - { url = "https://files.pythonhosted.org/packages/04/61/c0d4607f7799efa8b8ea3c49b4621e861c8f5c41fd4b5b636c534fcb7d73/sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", size = 2086475 }, - { url = "https://files.pythonhosted.org/packages/9d/8e/8344f8ae1cb6a479d0741c02cd4f666925b2bf02e2468ddaf5ce44111f30/sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", size = 2110903 }, - { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224 }, + { url = "https://files.pythonhosted.org/packages/37/4e/b00e3ffae32b74b5180e15d2ab4040531ee1bef4c19755fe7926622dc958/sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", size = 2121232, upload-time = "2025-05-14T17:48:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/6547ebb10875302074a37e1970a5dce7985240665778cfdee2323709f749/sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", size = 2110897, upload-time = "2025-05-14T17:48:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/9e/21/59df2b41b0f6c62da55cd64798232d7349a9378befa7f1bb18cf1dfd510a/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", size = 3273313, upload-time = "2025-05-14T17:51:56.205Z" }, + { url = "https://files.pythonhosted.org/packages/62/e4/b9a7a0e5c6f79d49bcd6efb6e90d7536dc604dab64582a9dec220dab54b6/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", size = 3273807, upload-time = "2025-05-14T17:55:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/d8/79f2427251b44ddee18676c04eab038d043cff0e764d2d8bb08261d6135d/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", size = 3209632, upload-time = "2025-05-14T17:51:59.384Z" }, + { url = "https://files.pythonhosted.org/packages/d4/16/730a82dda30765f63e0454918c982fb7193f6b398b31d63c7c3bd3652ae5/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", size = 3233642, upload-time = "2025-05-14T17:55:29.901Z" }, + { url = "https://files.pythonhosted.org/packages/04/61/c0d4607f7799efa8b8ea3c49b4621e861c8f5c41fd4b5b636c534fcb7d73/sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", size = 2086475, upload-time = "2025-05-14T17:56:02.095Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8e/8344f8ae1cb6a479d0741c02cd4f666925b2bf02e2468ddaf5ce44111f30/sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", size = 2110903, upload-time = "2025-05-14T17:56:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, ] [[package]] @@ -1257,9 +1281,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985 } +sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297 }, + { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, ] [[package]] @@ -1271,9 +1295,9 @@ dependencies = [ { name = "executing" }, { name = "pure-eval" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] [[package]] @@ -1284,9 +1308,9 @@ dependencies = [ { name = "anyio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948 } +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984 }, + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, ] [[package]] @@ -1296,18 +1320,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/f8/022d8704a3314f3e96dbd6bbd16ebe119ce30e35f41aabfa92345652fceb/syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4", size = 52492 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/f8/022d8704a3314f3e96dbd6bbd16ebe119ce30e35f41aabfa92345652fceb/syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4", size = 52492, upload-time = "2025-03-24T01:36:37.225Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/9d/aef9ec5fd5a4ee2f6a96032c4eda5888c5c7cec65cef6b28c4fc37671d88/syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda", size = 52214 }, + { url = "https://files.pythonhosted.org/packages/ec/9d/aef9ec5fd5a4ee2f6a96032c4eda5888c5c7cec65cef6b28c4fc37671d88/syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda", size = 52214, upload-time = "2025-03-24T01:36:35.278Z" }, ] [[package]] name = "tenacity" version = "8.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309 } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165 }, + { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, ] [[package]] @@ -1318,23 +1342,23 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991 } +sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987 }, - { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155 }, - { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898 }, - { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535 }, - { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548 }, - { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895 }, + { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload-time = "2025-02-14T06:02:14.174Z" }, + { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload-time = "2025-02-14T06:02:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload-time = "2025-02-14T06:02:16.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload-time = "2025-02-14T06:02:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload-time = "2025-02-14T06:02:20.729Z" }, + { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload-time = "2025-02-14T06:02:22.67Z" }, ] [[package]] name = "tokenize-rt" version = "6.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/ed/8f07e893132d5051d86a553e749d5c89b2a4776eb3a579b72ed61f8559ca/tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6", size = 5476 } +sdist = { url = "https://files.pythonhosted.org/packages/69/ed/8f07e893132d5051d86a553e749d5c89b2a4776eb3a579b72ed61f8559ca/tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6", size = 5476, upload-time = "2025-05-23T23:48:00.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44", size = 6004 }, + { url = "https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44", size = 6004, upload-time = "2025-05-23T23:47:58.812Z" }, ] [[package]] @@ -1344,37 +1368,37 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tomlkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/55/b128ee446606e9f2e49582bb1e462a255317401fd7adb0b2114e50006d01/toml_sort-0.24.2.tar.gz", hash = "sha256:20cb7c5e9de9c871990f1594f028aaf8bd0f78d7ce37995a22289dc157a45b79", size = 17973 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/55/b128ee446606e9f2e49582bb1e462a255317401fd7adb0b2114e50006d01/toml_sort-0.24.2.tar.gz", hash = "sha256:20cb7c5e9de9c871990f1594f028aaf8bd0f78d7ce37995a22289dc157a45b79", size = 17973, upload-time = "2024-11-19T14:03:05.092Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/55/7a7c02de848eef670bd018b62122ce32a6e227203e5a626c96d4935958a6/toml_sort-0.24.2-py3-none-any.whl", hash = "sha256:d81d299789a1fd9dd306a4021951eab5fc0c5486599e277fcf8142c7735f3308", size = 20162 }, + { url = "https://files.pythonhosted.org/packages/9b/55/7a7c02de848eef670bd018b62122ce32a6e227203e5a626c96d4935958a6/toml_sort-0.24.2-py3-none-any.whl", hash = "sha256:d81d299789a1fd9dd306a4021951eab5fc0c5486599e277fcf8142c7735f3308", size = 20162, upload-time = "2024-11-19T14:03:03.338Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] name = "tomlkit" version = "0.13.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901 }, + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, ] [[package]] @@ -1384,18 +1408,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] [[package]] name = "traitlets" version = "5.14.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] [[package]] @@ -1408,18 +1432,18 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625 } +sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 }, + { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, ] [[package]] name = "types-pyyaml" version = "6.0.12.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378, upload-time = "2025-05-16T03:08:04.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312 }, + { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312, upload-time = "2025-05-16T03:08:04.019Z" }, ] [[package]] @@ -1429,18 +1453,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027 } +sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644 }, + { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, ] [[package]] name = "typing-extensions" version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] @@ -1450,18 +1474,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] [[package]] name = "urllib3" version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] @@ -1472,38 +1496,38 @@ dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] [[package]] name = "wcwidth" version = "0.2.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] [[package]] name = "websockets" version = "15.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423 }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082 }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330 }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878 }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883 }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252 }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521 }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958 }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918 }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388 }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] [[package]] @@ -1513,7 +1537,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, ] From ad4fec923003711b174e50b9ddeba5afd9f06e92 Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Mon, 27 Oct 2025 21:23:37 +0100 Subject: [PATCH 029/253] Enhance dashboard functionality Added an overview (landing page showing current and available config) and advanced stats windows, controlled by top-right menu Stats calculation is no longer optional, but we use a trivial token estimator by default --- scripts/demo_run_tools.py | 5 +- src/serena/agent.py | 51 +- src/serena/analytics.py | 18 +- src/serena/config/serena_config.py | 7 +- src/serena/dashboard.py | 106 +++ src/serena/resources/dashboard/dashboard.css | 620 ++++++++++++++++++ src/serena/resources/dashboard/dashboard.js | 444 ++++++++++--- src/serena/resources/dashboard/index.html | 352 +++------- .../resources/serena_config.template.yml | 14 +- src/serena/tools/tools_base.py | 2 +- 10 files changed, 1234 insertions(+), 385 deletions(-) create mode 100644 src/serena/resources/dashboard/dashboard.css diff --git a/scripts/demo_run_tools.py b/scripts/demo_run_tools.py index d52cfb00..dad0c1cd 100644 --- a/scripts/demo_run_tools.py +++ b/scripts/demo_run_tools.py @@ -12,7 +12,7 @@ from serena.constants import REPO_ROOT from serena.tools import FindFileTool, FindReferencingSymbolsTool, GetSymbolsOverviewTool, SearchForPatternTool if __name__ == "__main__": - agent = SerenaAgent(project=REPO_ROOT, serena_config=SerenaConfig(gui_log_window_enabled=False, web_dashboard=False)) + agent = SerenaAgent(project=REPO_ROOT, serena_config=SerenaConfig(gui_log_window_enabled=False, web_dashboard=True)) # apply a tool find_refs_tool = agent.get_tool(FindReferencingSymbolsTool) @@ -21,6 +21,7 @@ if __name__ == "__main__": overview_tool = agent.get_tool(GetSymbolsOverviewTool) result = agent.execute_task( - lambda: overview_tool.apply("src/solidlsp/ls.py"), + lambda: overview_tool.apply_ex(relative_file_path="src/solidlsp/ls.py"), ) pprint(json.loads(result)) + # input("Press Enter to continue...") diff --git a/src/serena/agent.py b/src/serena/agent.py index dfe8c1ad..8236db55 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -127,25 +127,9 @@ class SerenaAgent: if self._gui_log_viewer is not None: self._gui_log_viewer.set_tool_names(tool_names) - self._tool_usage_stats: ToolUsageStats | None = None - if self.serena_config.record_tool_usage_stats: - token_count_estimator = RegisteredTokenCountEstimator[self.serena_config.token_count_estimator] - log.info(f"Tool usage statistics recording is enabled with token count estimator: {token_count_estimator.name}.") - self._tool_usage_stats = ToolUsageStats(token_count_estimator) - - # start the dashboard (web frontend), registering its log handler - if self.serena_config.web_dashboard: - self._dashboard_thread, port = SerenaDashboardAPI( - get_memory_log_handler(), tool_names, agent=self, tool_usage_stats=self._tool_usage_stats - ).run_in_thread() - dashboard_url = f"http://127.0.0.1:{port}/dashboard/index.html" - log.info("Serena web dashboard started at %s", dashboard_url) - if self.serena_config.web_dashboard_open_on_launch: - # open the dashboard URL in the default web browser (using a separate process to control - # output redirection) - process = multiprocessing.Process(target=self._open_dashboard, args=(dashboard_url,)) - process.start() - process.join(timeout=1) + token_count_estimator = RegisteredTokenCountEstimator[self.serena_config.token_count_estimator] + log.info(f"Will record tool usage statistics with token count estimator: {token_count_estimator.name}.") + self._tool_usage_stats = ToolUsageStats(token_count_estimator) # log fundamental information log.info(f"Starting Serena server (version={serena_version()}, process id={os.getpid()}, parent process id={os.getppid()})") @@ -192,6 +176,22 @@ class SerenaAgent: except Exception as e: log.error(f"Error activating project '{project}' at startup: {e}", exc_info=e) + # start the dashboard (web frontend), registering its log handler + # should be the last thing to happen in the initialization since the dashboard + # may access various parts of the agent + if self.serena_config.web_dashboard: + self._dashboard_thread, port = SerenaDashboardAPI( + get_memory_log_handler(), tool_names, agent=self, tool_usage_stats=self._tool_usage_stats + ).run_in_thread() + dashboard_url = f"http://127.0.0.1:{port}/dashboard/index.html" + log.info("Serena web dashboard started at %s", dashboard_url) + if self.serena_config.web_dashboard_open_on_launch: + # open the dashboard URL in the default web browser (using a separate process to control + # output redirection) + process = multiprocessing.Process(target=self._open_dashboard, args=(dashboard_url,)) + process.start() + process.join(timeout=1) + def get_context(self) -> SerenaAgentContext: return self._context @@ -234,18 +234,15 @@ class SerenaAgent: tool_inclusion_definitions.append(project.project_config) return tool_inclusion_definitions - def record_tool_usage_if_enabled(self, input_kwargs: dict, tool_result: str | dict, tool: Tool) -> None: + def record_tool_usage(self, input_kwargs: dict, tool_result: str | dict, tool: Tool) -> None: """ Record the usage of a tool with the given input and output strings if tool usage statistics recording is enabled. """ tool_name = tool.get_name() - if self._tool_usage_stats is not None: - input_str = str(input_kwargs) - output_str = str(tool_result) - log.debug(f"Recording tool usage for tool '{tool_name}'") - self._tool_usage_stats.record_tool_usage(tool_name, input_str, output_str) - else: - log.debug(f"Tool usage statistics recording is disabled, not recording usage of '{tool_name}'.") + input_str = str(input_kwargs) + output_str = str(tool_result) + log.debug(f"Recording tool usage for tool '{tool_name}'") + self._tool_usage_stats.record_tool_usage(tool_name, input_str, output_str) @staticmethod def _open_dashboard(url: str) -> None: diff --git a/src/serena/analytics.py b/src/serena/analytics.py index c773fc08..add6fd09 100644 --- a/src/serena/analytics.py +++ b/src/serena/analytics.py @@ -69,12 +69,26 @@ class AnthropicTokenCount(TokenCountEstimator): return self._send_count_tokens_request(text).input_tokens +class CharCountEstimator(TokenCountEstimator): + """ + A naive character count estimator that estimates tokens based on character count. + """ + + def __init__(self, avg_chars_per_token: int = 4): + self._avg_chars_per_token = avg_chars_per_token + + def estimate_token_count(self, text: str) -> int: + # Assuming an average of 4 characters per token + return len(text) // self._avg_chars_per_token + + _registered_token_estimator_instances_cache: dict[RegisteredTokenCountEstimator, TokenCountEstimator] = {} class RegisteredTokenCountEstimator(Enum): TIKTOKEN_GPT4O = "TIKTOKEN_GPT4O" ANTHROPIC_CLAUDE_SONNET_4 = "ANTHROPIC_CLAUDE_SONNET_4" + CHAR_COUNT = "CHAR_COUNT" @classmethod def get_valid_names(cls) -> list[str]: @@ -89,8 +103,10 @@ class RegisteredTokenCountEstimator(Enum): return TiktokenCountEstimator(model_name="gpt-4o") case RegisteredTokenCountEstimator.ANTHROPIC_CLAUDE_SONNET_4: return AnthropicTokenCount(model_name="claude-sonnet-4-20250514") + case RegisteredTokenCountEstimator.CHAR_COUNT: + return CharCountEstimator(avg_chars_per_token=4) case _: - raise ValueError(f"Unknown token count estimator: {self.value}") + raise ValueError(f"Unknown token count estimator: {self}") def load_estimator(self) -> TokenCountEstimator: estimator_instance = _registered_token_estimator_instances_cache.get(self) diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 5b9d4462..c462d561 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -340,10 +340,8 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): """ whether to apply JetBrains mode """ - record_tool_usage_stats: bool = False - """Whether to record tool usage statistics, they will be shown in the web dashboard if recording is active. - """ - token_count_estimator: str = RegisteredTokenCountEstimator.TIKTOKEN_GPT4O.name + + token_count_estimator: str = RegisteredTokenCountEstimator.CHAR_COUNT.name """Only relevant if `record_tool_usage` is True; the name of the token count estimator to use for tool usage statistics. See the `RegisteredTokenCountEstimator` enum for available options. @@ -457,7 +455,6 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): instance.excluded_tools = loaded_commented_yaml.get("excluded_tools", []) instance.included_optional_tools = loaded_commented_yaml.get("included_optional_tools", []) instance.jetbrains = loaded_commented_yaml.get("jetbrains", False) - instance.record_tool_usage_stats = loaded_commented_yaml.get("record_tool_usage_stats", False) instance.token_count_estimator = loaded_commented_yaml.get( "token_count_estimator", RegisteredTokenCountEstimator.TIKTOKEN_GPT4O.name ) diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index c4a5f91d..0468e0fa 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -39,6 +39,18 @@ class ResponseToolStats(BaseModel): stats: dict[str, dict[str, int]] +class ResponseConfigOverview(BaseModel): + active_project: dict[str, str | None] + context: dict[str, str] + modes: list[dict[str, str]] + active_tools: list[str] + tool_stats_summary: dict[str, dict[str, int]] + registered_projects: list[dict[str, str | bool]] + available_tools: list[dict[str, str | bool]] + available_modes: list[dict[str, str | bool]] + available_contexts: list[dict[str, str | bool]] + + class SerenaDashboardAPI: log = logging.getLogger(__qualname__) @@ -104,6 +116,11 @@ class SerenaDashboardAPI: estimator_name = self._tool_usage_stats.token_estimator_name if self._tool_usage_stats else "unknown" return {"token_count_estimator_name": estimator_name} + @self._app.route("/get_config_overview", methods=["GET"]) + def get_config_overview() -> dict[str, Any]: + result = self._get_config_overview() + return result.model_dump() + @self._app.route("/shutdown", methods=["PUT"]) def shutdown() -> dict[str, str]: self._shutdown() @@ -129,6 +146,95 @@ class SerenaDashboardAPI: if self._tool_usage_stats is not None: self._tool_usage_stats.clear() + def _get_config_overview(self) -> ResponseConfigOverview: + from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode + + # Get active project info + project = self._agent.get_active_project() + active_project_name = project.project_name if project else None + project_info = { + "name": active_project_name, + "language": project.project_config.language.value if project else None, + } + + # Get context info + context = self._agent.get_context() + context_info = { + "name": context.name, + "description": context.description, + } + + # Get active modes + modes = self._agent.get_active_modes() + modes_info = [{"name": mode.name, "description": mode.description} for mode in modes] + active_mode_names = [mode.name for mode in modes] + + # Get active tools + active_tools = self._agent.get_active_tool_names() + + # Get registered projects + registered_projects: list[dict[str, str | bool]] = [] + for proj in self._agent.serena_config.projects: + registered_projects.append( + { + "name": proj.project_name, + "path": str(proj.project_root), + "is_active": proj.project_name == active_project_name, + } + ) + + # Get all available tools (excluding active ones) + all_tool_names = sorted([tool.get_name_from_cls() for tool in self._agent._all_tools.values()]) + available_tools: list[dict[str, str | bool]] = [] + for tool_name in all_tool_names: + if tool_name not in active_tools: + available_tools.append( + { + "name": tool_name, + "is_active": False, + } + ) + + # Get all available modes + all_mode_names = SerenaAgentMode.list_registered_mode_names() + available_modes: list[dict[str, str | bool]] = [] + for mode_name in all_mode_names: + available_modes.append( + { + "name": mode_name, + "is_active": mode_name in active_mode_names, + } + ) + + # Get all available contexts + all_context_names = SerenaAgentContext.list_registered_context_names() + available_contexts: list[dict[str, str | bool]] = [] + for context_name in all_context_names: + available_contexts.append( + { + "name": context_name, + "is_active": context_name == context.name, + } + ) + + # Get basic tool stats (just num_calls for overview) + tool_stats_summary = {} + if self._tool_usage_stats is not None: + full_stats = self._tool_usage_stats.get_tool_stats_dict() + tool_stats_summary = {name: {"num_calls": stats["num_times_called"]} for name, stats in full_stats.items()} + + return ResponseConfigOverview( + active_project=project_info, + context=context_info, + modes=modes_info, + active_tools=active_tools, + tool_stats_summary=tool_stats_summary, + registered_projects=registered_projects, + available_tools=available_tools, + available_modes=available_modes, + available_contexts=available_contexts, + ) + def _shutdown(self) -> None: log.info("Shutting down Serena") if self._shutdown_callback: diff --git a/src/serena/resources/dashboard/dashboard.css b/src/serena/resources/dashboard/dashboard.css new file mode 100644 index 00000000..6f26f466 --- /dev/null +++ b/src/serena/resources/dashboard/dashboard.css @@ -0,0 +1,620 @@ +:root { + /* Light theme variables */ + --bg-primary: #f5f5f5; + --bg-secondary: #ffffff; + --text-primary: #000000; + --text-secondary: #333333; + --text-muted: #666666; + --border-color: #ddd; + --btn-primary: #eaa45d; + --btn-hover: #dca662; + --btn-disabled: #6c757d; + --shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + --tool-highlight: #ffff00; + --tool-highlight-text: #000000; + --log-debug: #808080; + --log-info: #000000; + --log-warning: #FF8C00; + --log-error: #FF0000; + --stats-header: #f8f9fa; +} + +[data-theme="dark"] { + /* Dark theme variables */ + --bg-primary: #1a1a1a; + --bg-secondary: #2d2d2d; + --text-primary: #ffffff; + --text-secondary: #e0e0e0; + --text-muted: #b0b0b0; + --border-color: #444; + --btn-primary: #eaa45d; + --btn-hover: #dca662; + --btn-disabled: #6c757d; + --shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + --tool-highlight: #ffd700; + --tool-highlight-text: #000000; + --log-debug: #808080; + --log-info: #ffffff; + --log-warning: #FF8C00; + --log-error: #FF0000; + --stats-header: #3a3a3a; +} + +body { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + margin: 0; + padding: 20px; + padding-top: 130px; + background-color: var(--bg-primary); + color: var(--text-primary); + transition: background-color 0.3s ease, color 0.3s ease; +} + +.header { + position: fixed; + top: 0; + left: 0; + right: 0; + background-color: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + padding: 20px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 1000; + transition: background-color 0.3s ease, border-color 0.3s ease; + min-height: 70px; + box-shadow: var(--shadow); +} + +.logo-container { + flex: 1; +} + +.logo-container img { + max-width: 300px; + height: auto; + display: block; +} + +.header-nav { + position: relative; + display: flex; + align-items: center; + gap: 10px; +} + +.menu-button { + background-color: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease, border-color 0.3s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.menu-button:hover { + background-color: var(--border-color); +} + +.menu-dropdown { + position: absolute; + top: 45px; + right: 0; + background-color: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 4px; + box-shadow: var(--shadow); + min-width: 200px; + z-index: 999; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.menu-dropdown a { + display: block; + padding: 12px 20px; + color: var(--text-primary); + text-decoration: none; + transition: background-color 0.3s ease; +} + +.menu-dropdown a:hover { + background-color: var(--border-color); +} + +.menu-dropdown a.active { + background-color: var(--btn-primary); + color: white; +} + +.menu-dropdown hr { + border: none; + border-top: 1px solid var(--border-color); + margin: 5px 0; +} + +.page-view { + max-width: 1600px; + margin: 0 auto; +} + +/* Overview Page Layout */ +.overview-container { + display: grid; + grid-template-columns: 1fr 400px; + gap: 20px; +} + +.overview-left { + min-width: 0; +} + +.overview-right { + min-width: 0; +} + +/* Overview Page Styles */ +.config-section, +.basic-stats-section, +.projects-section { + background-color: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 5px; + padding: 20px; + margin-bottom: 20px; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.config-section h2, +.basic-stats-section h2 { + margin-top: 0; + color: var(--text-secondary); +} + +/* Collapsible Headers */ +.collapsible-header { + margin-top: 0; + margin-bottom: 0; + font-size: 18px; + color: var(--text-secondary); + cursor: pointer; + user-select: none; + display: flex; + justify-content: space-between; + align-items: center; +} + +.collapsible-header:hover { + color: var(--text-primary); +} + +.toggle-icon { + transition: transform 0.3s ease; + font-size: 14px; +} + +.toggle-icon.expanded { + transform: rotate(-180deg); +} + +.collapsible-content { + margin-top: 15px; + max-height: 400px; + overflow-y: auto; +} + +.config-grid { + display: grid; + grid-template-columns: 180px 1fr; + gap: 12px; + margin-bottom: 20px; +} + +.config-label { + font-weight: bold; + color: var(--text-secondary); +} + +.config-value { + color: var(--text-primary); +} + +.config-list { + list-style: none; + padding: 0; + margin: 0; +} + +.config-list li { + padding: 4px 0; +} + +.tools-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 8px; + margin-top: 10px; +} + +.tool-item { + background-color: var(--bg-primary); + padding: 6px 10px; + border-radius: 3px; + font-size: 13px; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: default; +} + +/* Projects List */ +.project-item { + padding: 10px 12px; + margin: 5px 0; + border-radius: 4px; + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + transition: background-color 0.2s ease; +} + +.project-item:hover { + background-color: var(--border-color); +} + +.project-item.active { + background-color: var(--btn-primary); + color: white; + border-color: var(--btn-primary); +} + +.project-name { + font-weight: bold; + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.project-path { + font-size: 11px; + color: var(--text-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.project-item.active .project-path { + color: rgba(255, 255, 255, 0.8); +} + +/* Generic Item Styles for Tools/Modes/Contexts */ +.info-item { + padding: 8px 12px; + margin: 5px 0; + border-radius: 4px; + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + transition: background-color 0.2s ease; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: default; +} + +.info-item:hover { + background-color: var(--border-color); +} + +.info-item.active { + background-color: var(--btn-primary); + color: white; + border-color: var(--btn-primary); + font-weight: bold; +} + +/* Basic Stats Styles */ +.basic-stats-section { + background-color: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 5px; + padding: 20px; + margin-bottom: 20px; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.basic-stats-section h2 { + margin-top: 0; + color: var(--text-secondary); +} + +.stat-bar-container { + display: flex; + align-items: center; + margin: 8px 0; + gap: 12px; +} + +.stat-tool-name { + min-width: 200px; + max-width: 200px; + font-weight: bold; + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: default; +} + +.bar-wrapper { + flex: 1; + height: 24px; + background-color: var(--border-color); + border-radius: 3px; + overflow: hidden; + position: relative; +} + +.bar { + height: 100%; + background-color: var(--btn-primary); + transition: width 0.5s ease; + border-radius: 3px; +} + +.stat-count { + min-width: 60px; + text-align: right; + font-weight: bold; + color: var(--text-primary); +} + +.no-stats-message { + text-align: center; + color: var(--text-muted); + font-style: italic; + padding: 20px; +} + +/* Log Container Styles */ +.log-container { + background-color: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 5px; + height: calc(100vh - 260px); + max-height: 700px; + overflow-y: auto; + overflow-x: auto; + padding: 10px; + white-space: pre-wrap; + font-size: 12px; + line-height: 1.4; + color: var(--text-primary); + transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease; +} + +.controls { + position: sticky; + top: 90px; + z-index: 100; + background-color: var(--bg-primary); + padding: 10px 0; + margin-bottom: 10px; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + flex-wrap: wrap; + transition: background-color 0.3s ease; +} + +.btn { + background-color: var(--btn-primary); + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s ease; +} + +.btn:hover { + background-color: var(--btn-hover); +} + +.btn:disabled { + background-color: var(--btn-disabled); + cursor: not-allowed; +} + +.theme-toggle { + display: flex; + align-items: center; + gap: 5px; + background-color: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.theme-toggle:hover { + background-color: var(--border-color); +} + +.theme-toggle span { + line-height: 1; +} + +.log-debug { + color: var(--log-debug); +} + +.log-info { + color: var(--log-info); +} + +.log-warning { + color: var(--log-warning); +} + +.log-error { + color: var(--log-error); +} + +.log-default { + color: var(--log-info); +} + +/* Tool name highlighting */ +.tool-name { + background-color: var(--tool-highlight); + color: var(--tool-highlight-text); + font-weight: bold; +} + +.loading { + text-align: center; + color: var(--text-muted); + font-style: italic; +} + +.error-message { + color: var(--log-error); + text-align: center; + margin: 10px 0; +} + +/* Advanced Stats Styles */ +.charts-container { + display: flex; + flex-wrap: wrap; + gap: 15px; + justify-content: space-between; + max-width: 1400px; + margin: 0 auto; +} + +.chart-group { + flex: 1; + min-width: 280px; + max-width: 320px; + text-align: center; +} + +.chart-wide { + flex: 0 0 100%; + min-width: 100%; + margin-top: 10px; +} + +.chart-group h3 { + margin: 0 0 10px 0; + color: var(--text-secondary); +} + +.stats-summary { + margin: 0 auto; + border-collapse: collapse; + background: var(--bg-secondary); + border-radius: 5px; + overflow: hidden; + box-shadow: var(--shadow); + transition: background-color 0.3s ease, box-shadow 0.3s ease; +} + +.stats-summary th, +.stats-summary td { + padding: 10px 20px; + text-align: left; + border-bottom: 1px solid var(--border-color); + color: var(--text-primary); + transition: border-color 0.3s ease, color 0.3s ease; +} + +.stats-summary th { + background-color: var(--stats-header); + font-weight: bold; + transition: background-color 0.3s ease; +} + +.stats-summary tr:last-child td { + border-bottom: none; +} + +@media (max-width: 1024px) { + .overview-container { + grid-template-columns: 1fr; + } + + .overview-right { + order: -1; + } +} + +@media (max-width: 768px) { + body { + padding-top: 140px; + } + + .header { + flex-direction: column; + gap: 10px; + padding: 10px 15px; + } + + .logo-container { + width: 100%; + text-align: center; + } + + .logo-container img { + max-width: 200px; + } + + .header-nav { + width: 100%; + justify-content: center; + } + + .charts-container { + flex-direction: column; + } + + .chart-group, + .chart-wide { + min-width: auto; + max-width: none; + } + + .controls { + flex-direction: column; + gap: 5px; + } + + .config-grid { + grid-template-columns: 1fr; + } + + .tools-grid { + grid-template-columns: 1fr; + } + + .stat-bar-container { + flex-wrap: wrap; + } + + .stat-tool-name { + min-width: 100%; + } +} diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index 5ce12df4..b1c53bc3 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -30,7 +30,7 @@ class LogMessage { } escapeHtml (convertString) { - if (typeof convertString !== 'string') return convertString; + if (typeof convertString !== 'string') return convertString; const patterns = { '<' : '<', @@ -49,47 +49,322 @@ class Dashboard { constructor() { let self = this; + // Page state + this.currentPage = 'overview'; + this.configData = null; + + // Tool names and stats this.toolNames = []; this.currentMaxIdx = -1; this.pollInterval = null; + this.configPollInterval = null; this.failureCount = 0; + + // jQuery elements this.$logContainer = $('#log-container'); this.$errorContainer = $('#error-container'); this.$loadButton = $('#load-logs'); - this.$shutdownButton = $('#shutdown'); - this.$toggleStats = $('#toggle-stats'); - this.$statsSection = $('#stats-section'); - this.$refreshStats = $('#refresh-stats'); - this.$clearStats = $('#clear-stats'); + this.$menuToggle = $('#menu-toggle'); + this.$menuDropdown = $('#menu-dropdown'); + this.$menuShutdown = $('#menu-shutdown'); this.$themeToggle = $('#theme-toggle'); this.$themeIcon = $('#theme-icon'); this.$themeText = $('#theme-text'); + this.$configDisplay = $('#config-display'); + this.$basicStatsDisplay = $('#basic-stats-display'); + this.$statsSection = $('#stats-section'); + this.$refreshStats = $('#refresh-stats'); + this.$clearStats = $('#clear-stats'); + this.$projectsDisplay = $('#projects-display'); + this.$projectsHeader = $('#projects-header'); + this.$availableToolsDisplay = $('#available-tools-display'); + this.$availableModesDisplay = $('#available-modes-display'); + this.$availableContextsDisplay = $('#available-contexts-display'); + // Chart references this.countChart = null; this.tokensChart = null; this.inputChart = null; this.outputChart = null; - // register event handlers + // Register event handlers this.$loadButton.click(this.loadLogs.bind(this)); - this.$shutdownButton.click(this.shutdown.bind(this)); - this.$toggleStats.click(this.toggleStats.bind(this)); + this.$menuShutdown.click(this.shutdown.bind(this)); + this.$menuToggle.click(this.toggleMenu.bind(this)); + this.$themeToggle.click(this.toggleTheme.bind(this)); this.$refreshStats.click(this.loadStats.bind(this)); this.$clearStats.click(this.clearStats.bind(this)); - this.$themeToggle.click(this.toggleTheme.bind(this)); - // initialize theme + // Page navigation + $('[data-page]').click(function(e) { + e.preventDefault(); + const page = $(this).data('page'); + self.navigateToPage(page); + }); + + // Close menu when clicking outside + $(document).click(function(e) { + if (!$(e.target).closest('.header-nav').length) { + self.$menuDropdown.hide(); + } + }); + + // Collapsible sections + $('.collapsible-header').click(function() { + const $header = $(this); + const $content = $header.next('.collapsible-content'); + const $icon = $header.find('.toggle-icon'); + + $content.slideToggle(300); + $icon.toggleClass('expanded'); + }); + + // Initialize theme this.initializeTheme(); - // initialize the application + // Initialize the application this.loadToolNames().then(function() { - // Load logs on page load after tool names are loaded - self.loadLogs(); + // Start on overview page + self.loadConfigOverview(); + self.startConfigPolling(); }); } + toggleMenu() { + this.$menuDropdown.toggle(); + } + + navigateToPage(page) { + // Hide menu + this.$menuDropdown.hide(); + + // Hide all pages + $('.page-view').hide(); + + // Show selected page + $('#page-' + page).show(); + + // Update menu active state + $('[data-page]').removeClass('active'); + $('[data-page="' + page + '"]').addClass('active'); + + // Update current page + this.currentPage = page; + + // Stop all polling + this.stopPolling(); + + // Start appropriate polling for the page + if (page === 'overview') { + this.loadConfigOverview(); + this.startConfigPolling(); + } else if (page === 'logs') { + this.loadLogs(); + } else if (page === 'stats') { + this.loadStats(); + } + } + + stopPolling() { + if (this.pollInterval) { + clearInterval(this.pollInterval); + this.pollInterval = null; + } + if (this.configPollInterval) { + clearInterval(this.configPollInterval); + this.configPollInterval = null; + } + } + + // ===== Config Overview Methods ===== + + loadConfigOverview() { + let self = this; + $.ajax({ + url: '/get_config_overview', + type: 'GET', + success: function(response) { + self.configData = response; + self.displayConfig(response); + self.displayBasicStats(response.tool_stats_summary); + self.displayProjects(response.registered_projects); + self.displayAvailableTools(response.available_tools); + self.displayAvailableModes(response.available_modes); + self.displayAvailableContexts(response.available_contexts); + }, + error: function(xhr, status, error) { + console.error('Error loading config overview:', error); + self.$configDisplay.html('
Error loading configuration
'); + self.$basicStatsDisplay.html('
Error loading stats
'); + self.$projectsDisplay.html('
Error loading projects
'); + self.$availableToolsDisplay.html('
Error loading tools
'); + self.$availableModesDisplay.html('
Error loading modes
'); + self.$availableContextsDisplay.html('
Error loading contexts
'); + } + }); + } + + startConfigPolling() { + // Poll every 2 seconds for config updates + this.configPollInterval = setInterval(this.loadConfigOverview.bind(this), 2000); + } + + displayConfig(config) { + // Check if tools section is currently expanded + const $existingContent = $('#tools-content'); + const wasExpanded = $existingContent.is(':visible'); + + let html = '
'; + + // Project info + html += '
Active Project:
'; + html += '
' + (config.active_project.name || 'None') + '
'; + + html += '
Language:
'; + html += '
' + (config.active_project.language || 'N/A') + '
'; + + // Context info + html += '
Context:
'; + html += '
' + config.context.name + '
'; + + // Modes info + html += '
Active Modes:
'; + html += '
'; + if (config.modes.length > 0) { + html += '
    '; + config.modes.forEach(function(mode) { + html += '
  • ' + mode.name + '
  • '; + }); + html += '
'; + } else { + html += 'None'; + } + html += '
'; + + html += '
'; + + // Active tools - collapsible + html += '
'; + html += '

'; + html += 'Active Tools (' + config.active_tools.length + ')'; + html += '▼'; + html += '

'; + html += '
'; + config.active_tools.forEach(function(tool) { + html += '
' + tool + '
'; + }); + html += '
'; + html += '
'; + + this.$configDisplay.html(html); + + // Re-attach collapsible handler for the newly created tools header + $('#tools-header').click(function() { + const $header = $(this); + const $content = $('#tools-content'); + const $icon = $header.find('.toggle-icon'); + + $content.slideToggle(300); + $icon.toggleClass('expanded'); + }); + } + + displayBasicStats(stats) { + if (Object.keys(stats).length === 0) { + this.$basicStatsDisplay.html('
No tool usage stats collected yet.
'); + return; + } + + // Sort tools by call count (descending) + const sortedTools = Object.keys(stats).sort((a, b) => { + return stats[b].num_calls - stats[a].num_calls; + }); + + const maxCalls = Math.max(...sortedTools.map(tool => stats[tool].num_calls)); + + let html = ''; + sortedTools.forEach(function(toolName) { + const count = stats[toolName].num_calls; + const percentage = maxCalls > 0 ? (count / maxCalls * 100) : 0; + + html += '
'; + html += '
' + toolName + '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
' + count + '
'; + html += '
'; + }); + + this.$basicStatsDisplay.html(html); + } + + displayProjects(projects) { + if (!projects || projects.length === 0) { + this.$projectsDisplay.html('
No projects registered.
'); + return; + } + + let html = ''; + projects.forEach(function(project) { + const activeClass = project.is_active ? ' active' : ''; + html += '
'; + html += '
' + project.name + '
'; + html += '
' + project.path + '
'; + html += '
'; + }); + + this.$projectsDisplay.html(html); + } + + displayAvailableTools(tools) { + if (!tools || tools.length === 0) { + this.$availableToolsDisplay.html('
All tools are active.
'); + return; + } + + let html = ''; + tools.forEach(function(tool) { + html += '
' + tool.name + '
'; + }); + + this.$availableToolsDisplay.html(html); + } + + displayAvailableModes(modes) { + if (!modes || modes.length === 0) { + this.$availableModesDisplay.html('
No modes available.
'); + return; + } + + let html = ''; + modes.forEach(function(mode) { + const activeClass = mode.is_active ? ' active' : ''; + html += '
' + mode.name + '
'; + }); + + this.$availableModesDisplay.html(html); + } + + displayAvailableContexts(contexts) { + if (!contexts || contexts.length === 0) { + this.$availableContextsDisplay.html('
No contexts available.
'); + return; + } + + let html = ''; + contexts.forEach(function(context) { + const activeClass = context.is_active ? ' active' : ''; + html += '
' + context.name + '
'; + }); + + this.$availableContextsDisplay.html(html); + } + + // ===== Logs Methods ===== + displayLogMessage(message) { - $('#log-container').append(new LogMessage(message, this.toolNames).$elem); + this.$logContainer.append(new LogMessage(message, this.toolNames).$elem); } loadToolNames() { @@ -227,16 +502,7 @@ class Dashboard { this.pollInterval = setInterval(this.pollForNewLogs.bind(this), 1000); } - toggleStats() { - if (this.$statsSection.is(':visible')) { - this.$statsSection.hide(); - this.$toggleStats.text('Show Stats'); - } else { - this.$statsSection.show(); - this.$toggleStats.text('Hide Stats'); - this.loadStats(); - } - } + // ===== Stats Methods ===== loadStats() { let self = this; @@ -252,7 +518,6 @@ class Dashboard { }); } - clearStats() { let self = this; $.ajax({ @@ -292,12 +557,12 @@ class Dashboard { const inputTokens = names.map(n => stats[n].input_tokens); const outputTokens = names.map(n => stats[n].output_tokens); const totalTokens = names.map(n => stats[n].input_tokens + stats[n].output_tokens); - + // Calculate totals for summary table const totalCalls = counts.reduce((sum, count) => sum + count, 0); const totalInputTokens = inputTokens.reduce((sum, tokens) => sum + tokens, 0); const totalOutputTokens = outputTokens.reduce((sum, tokens) => sum + tokens, 0); - + // Generate consistent colors for tools const colors = this.generateColors(names.length); @@ -325,16 +590,16 @@ class Dashboard { // Tool calls pie chart this.countChart = new Chart(countCtx, { type: 'pie', - data: { - labels: names, - datasets: [{ + data: { + labels: names, + datasets: [{ data: counts, backgroundColor: colors - }] + }] }, options: { plugins: { - legend: { + legend: { display: true, labels: { color: textColor @@ -353,16 +618,16 @@ class Dashboard { // Input tokens pie chart this.inputChart = new Chart(inputCtx, { type: 'pie', - data: { - labels: names, - datasets: [{ + data: { + labels: names, + datasets: [{ data: inputTokens, backgroundColor: colors - }] + }] }, options: { plugins: { - legend: { + legend: { display: true, labels: { color: textColor @@ -381,16 +646,16 @@ class Dashboard { // Output tokens pie chart this.outputChart = new Chart(outputCtx, { type: 'pie', - data: { - labels: names, - datasets: [{ + data: { + labels: names, + datasets: [{ data: outputTokens, backgroundColor: colors - }] + }] }, options: { plugins: { - legend: { + legend: { display: true, labels: { color: textColor @@ -409,11 +674,11 @@ class Dashboard { // Combined input/output tokens bar chart this.tokensChart = new Chart(tokensCtx, { type: 'bar', - data: { - labels: names, + data: { + labels: names, datasets: [ - { - label: 'Input Tokens', + { + label: 'Input Tokens', data: inputTokens, backgroundColor: colors.map(color => color + '80'), // Semi-transparent borderColor: colors, @@ -421,8 +686,8 @@ class Dashboard { borderSkipped: false, yAxisID: 'y' }, - { - label: 'Output Tokens', + { + label: 'Output Tokens', data: outputTokens, backgroundColor: colors, yAxisID: 'y1' @@ -452,8 +717,8 @@ class Dashboard { display: true, position: 'left', beginAtZero: true, - title: { - display: true, + title: { + display: true, text: 'Input Tokens', color: textColor }, @@ -469,15 +734,15 @@ class Dashboard { display: true, position: 'right', beginAtZero: true, - title: { - display: true, + title: { + display: true, text: 'Output Tokens', color: textColor }, ticks: { color: textColor }, - grid: { + grid: { drawOnChartArea: false, color: gridColor } @@ -508,10 +773,12 @@ class Dashboard { $('#stats-summary').html(tableHtml); } + // ===== Theme Methods ===== + initializeTheme() { // Check if user has manually set a theme preference const savedTheme = localStorage.getItem('serena-theme'); - + if (savedTheme) { // User has manually set a preference, use it this.setTheme(savedTheme); @@ -519,7 +786,7 @@ class Dashboard { // No manual preference, detect system color scheme this.detectSystemTheme(); } - + // Listen for system theme changes this.setupSystemThemeListener(); } @@ -534,7 +801,7 @@ class Dashboard { setupSystemThemeListener() { // Listen for changes in system color scheme const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - + const handleSystemThemeChange = (e) => { // Only auto-switch if user hasn't manually set a preference const savedTheme = localStorage.getItem('serena-theme'); @@ -543,7 +810,7 @@ class Dashboard { this.setTheme(newTheme); } }; - + // Add listener for system theme changes if (mediaQuery.addEventListener) { mediaQuery.addEventListener('change', handleSystemThemeChange); @@ -556,7 +823,7 @@ class Dashboard { toggleTheme() { const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; const newTheme = currentTheme === 'light' ? 'dark' : 'light'; - + // When user manually toggles, save their preference localStorage.setItem('serena-theme', newTheme); this.setTheme(newTheme); @@ -565,8 +832,8 @@ class Dashboard { setTheme(theme) { // Set the theme on the document element document.documentElement.setAttribute('data-theme', theme); - - // Update the toggle button + + // Update the theme toggle button if (theme === 'dark') { this.$themeIcon.text('☀️'); this.$themeText.text('Light'); @@ -574,13 +841,13 @@ class Dashboard { this.$themeIcon.text('🌙'); this.$themeText.text('Dark'); } - + // Update the logo based on theme this.updateLogo(theme); - + // Save to localStorage localStorage.setItem('serena-theme', theme); - + // Update charts if they exist this.updateChartsTheme(); } @@ -600,43 +867,47 @@ class Dashboard { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; const textColor = isDark ? '#ffffff' : '#000000'; const gridColor = isDark ? '#444' : '#ddd'; - - // Update existing charts - if (this.countChart) { - this.countChart.options.scales.x.ticks.color = textColor; - this.countChart.options.scales.y.ticks.color = textColor; - this.countChart.options.scales.x.grid.color = gridColor; - this.countChart.options.scales.y.grid.color = gridColor; + + // Update existing charts if they exist and have the scales property + if (this.countChart && this.countChart.options.plugins) { + if (this.countChart.options.plugins.legend) { + this.countChart.options.plugins.legend.labels.color = textColor; + } this.countChart.update(); } - - if (this.inputChart) { - this.inputChart.options.scales.x.ticks.color = textColor; - this.inputChart.options.scales.y.ticks.color = textColor; - this.inputChart.options.scales.x.grid.color = gridColor; - this.inputChart.options.scales.y.grid.color = gridColor; + + if (this.inputChart && this.inputChart.options.plugins) { + if (this.inputChart.options.plugins.legend) { + this.inputChart.options.plugins.legend.labels.color = textColor; + } this.inputChart.update(); } - - if (this.outputChart) { - this.outputChart.options.scales.x.ticks.color = textColor; - this.outputChart.options.scales.y.ticks.color = textColor; - this.outputChart.options.scales.x.grid.color = gridColor; - this.outputChart.options.scales.y.grid.color = gridColor; + + if (this.outputChart && this.outputChart.options.plugins) { + if (this.outputChart.options.plugins.legend) { + this.outputChart.options.plugins.legend.labels.color = textColor; + } this.outputChart.update(); } - - if (this.tokensChart) { + + if (this.tokensChart && this.tokensChart.options.scales) { this.tokensChart.options.scales.x.ticks.color = textColor; this.tokensChart.options.scales.y.ticks.color = textColor; this.tokensChart.options.scales.y1.ticks.color = textColor; this.tokensChart.options.scales.x.grid.color = gridColor; this.tokensChart.options.scales.y.grid.color = gridColor; this.tokensChart.options.scales.y1.grid.color = gridColor; + this.tokensChart.options.scales.y.title.color = textColor; + this.tokensChart.options.scales.y1.title.color = textColor; + if (this.tokensChart.options.plugins && this.tokensChart.options.plugins.legend) { + this.tokensChart.options.plugins.legend.labels.color = textColor; + } this.tokensChart.update(); } } + // ===== Shutdown Method ===== + shutdown() { const self = this; const _shutdown = function () { @@ -658,5 +929,8 @@ class Dashboard { } else { console.log("Shutdown cancelled"); } + + // Close menu + self.$menuDropdown.hide(); } } diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html index a303839a..9f0670e6 100644 --- a/src/serena/resources/dashboard/index.html +++ b/src/serena/resources/dashboard/index.html @@ -8,271 +8,112 @@ + - -
- -
+
+
+ +
+ +
-
- - - -
- 🌙 - Dark + +
+
+
+
+

Current Configuration

+
+
Loading configuration...
+
+
+ +
+

Tool Usage

+
+
Loading stats...
+
+
+
+ +
+
+

+ Registered Projects + ▼ +

+ +
+ +
+

+ Available Tools (Disabled) + ▼ +

+ +
+ +
+

+ Available Modes + ▼ +

+ +
+ +
+

+ Available Contexts + ▼ +

+ +
+
-
-
+ + -