Memory names should be descriptive and use underscores instead of spaces (e.g., "api_architecture",
- "testing_guidelines").
+ "testing_guidelines"). Use "/" to organize memories into subdirectories (e.g., "architecture/api_design"),
+ use the "global/" prefix to write memories that are shared across projects (e.g., "global/java/style_guide").
diff --git a/src/serena/resources/dashboard/news/20260303.html b/src/serena/resources/dashboard/news/20260303.html
new file mode 100644
index 00000000..ca91377f
--- /dev/null
+++ b/src/serena/resources/dashboard/news/20260303.html
@@ -0,0 +1,12 @@
+
+
Nested and Global Memories
+
March 03, 2026
+
+ Serena's memory system has been significantly extended in functionality.
+ It keeps the simplicity that made it popular but now supports structuring memories
+ into topics and sharing them across projects.
+
+
+ For more detailed information, see the documentation.
+
+
\ No newline at end of file
diff --git a/src/serena/resources/project.local.template.yml b/src/serena/resources/project.local.template.yml
new file mode 100644
index 00000000..36cd3def
--- /dev/null
+++ b/src/serena/resources/project.local.template.yml
@@ -0,0 +1,5 @@
+# This file allows you to locally override settings in project.yml for development purposes.
+#
+# Use the same keys as in project.yml here. Any setting you specify will override the corresponding
+# setting in project.yml, allowing you to customise the configuration for your local development environment
+# without affecting the project configuration in project.yml (which is intended to be versioned).
diff --git a/src/serena/resources/project.template.yml b/src/serena/resources/project.template.yml
index afbb0aca..f6ba8cb1 100644
--- a/src/serena/resources/project.template.yml
+++ b/src/serena/resources/project.template.yml
@@ -31,6 +31,11 @@ languages: ["python"]
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
+# line ending convention to use when writing source files.
+# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
+# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
+line_ending:
+
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
@@ -41,6 +46,12 @@ language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
+# advanced configuration option allowing to configure language server-specific options.
+# Maps the language key to the options.
+# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
+# No documentation on options means no options are available.
+ls_specific_settings: {}
+
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
@@ -51,7 +62,9 @@ ignored_paths: []
# Added on 2025-04-18
read_only: false
-# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
+# list of tool names to exclude.
+# This extends the existing exclusions (e.g. from the global configuration)
+#
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
@@ -92,7 +105,8 @@ read_only: false
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []
-# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
+# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
+# This extends the existing inclusions (e.g. from the global configuration).
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
@@ -123,3 +137,15 @@ initial_prompt: ""
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
+
+# list of regex patterns which, when matched, mark a memory entry as read‑only.
+# Extends the list from the global configuration, merging the two lists.
+read_only_memory_patterns: []
+
+# list of regex patterns for memories to completely ignore.
+# Matching memories will not appear in list_memories or activate_project output
+# and cannot be accessed via read_memory or write_memory.
+# To access ignored memory files, use the read_file tool on the raw file path.
+# Extends the list from the global configuration, merging the two lists.
+# Example: ["_archive/.*", "_episodes/.*"]
+ignored_memory_patterns: []
diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml
index 9b369089..7ec556db 100644
--- a/src/serena/resources/serena_config.template.yml
+++ b/src/serena/resources/serena_config.template.yml
@@ -7,6 +7,12 @@
# in your IDE).
language_backend: LSP
+# line ending convention to use when writing source files.
+# Possible values: "lf" (Unix), "crlf" (Windows), "native" (platform default).
+# Note that Serena's own files (e.g. memories and configuration files) always use native line endings.
+# This setting can be overridden on a per-project basis in project.yml files.
+line_ending: native
+
# whether to open a graphical window with Serena's logs.
# This is mainly supported on Windows and (partly) on Linux; not available on macOS.
# If you prefer a browser-based tool, use the `web_dashboard` option instead.
@@ -68,6 +74,20 @@ ls_specific_settings: {}
# These patterns are merged additively with each project's own ignored_paths.
ignored_paths: []
+# list of regex patterns which, when matched, mark a memory entry as read‑only.
+# For example, "global/.*" will mark all global memories as read-only.
+# You can extend the list on a per-project basis in the project.yml configuration file.
+read_only_memory_patterns: []
+
+# list of regex patterns for memories to completely ignore.
+# Matching memories will not appear in list_memories or activate_project output
+# and cannot be accessed via read_memory or write_memory.
+# To access ignored memory files, use the read_file tool on the raw file path.
+# This is useful for projects with large numbers of archived memory files.
+# You can extend the list on a per-project basis in the project.yml configuration file.
+# Example: ["_archive/.*", "_episodes/.*"]
+ignored_memory_patterns: []
+
# timeout, in seconds, after which tool executions are terminated
tool_timeout: 240
@@ -120,5 +140,13 @@ token_count_estimator: CHAR_COUNT
# Can be overridden in project.yml.
symbol_info_budget: 10
+# template for the location of the per-project .serena data folder (memories, caches, etc.).
+# Supports the following placeholders:
+# $projectDir - the absolute path to the project root directory
+# $projectFolderName - the name of the project directory
+# Default: "$projectDir/.serena" (data stored inside the project directory)
+# Example for a central location: "/projects-metadata/$projectFolderName/.serena"
+project_serena_folder_location: "$projectDir/.serena"
+
# the list of registered project paths (updated automatically).
projects: []
diff --git a/src/serena/symbol.py b/src/serena/symbol.py
index 7d3bed78..f8ba9dd2 100644
--- a/src/serena/symbol.py
+++ b/src/serena/symbol.py
@@ -2,10 +2,10 @@ import json
import logging
import os
from abc import ABC, abstractmethod
-from collections.abc import Callable, Iterator, Sequence
+from collections.abc import Callable, Iterable, Iterator, Sequence
from dataclasses import asdict, dataclass
from time import perf_counter
-from typing import TYPE_CHECKING, Any, Generic, Literal, NotRequired, Self, TypedDict, TypeVar, Union
+from typing import Any, Generic, Literal, NotRequired, Self, TypedDict, TypeVar
from sensai.util.string import ToStringMixin
@@ -18,9 +18,6 @@ from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation
from .ls_manager import LanguageServerManager
from .project import Project
-if TYPE_CHECKING:
- from .agent import SerenaAgent
-
log = logging.getLogger(__name__)
NAME_PATH_SEP = "/"
@@ -536,19 +533,12 @@ class ReferenceInLanguageServerSymbol(ToStringMixin):
class LanguageServerSymbolRetriever:
- def __init__(self, ls: SolidLanguageServer | LanguageServerManager, agent: Union["SerenaAgent", None] = None) -> None:
+ def __init__(self, project: Project) -> None:
"""
- :param ls: the language server or language server manager to use for symbol retrieval and editing operations.
- :param agent: the agent to use (only needed for marking files as modified). You can pass None if you don't
- need an agent to be aware of file modifications performed by the symbol manager.
+ :param project: the project instance
"""
- if isinstance(ls, SolidLanguageServer):
- ls_manager = LanguageServerManager({ls.language: ls})
- else:
- ls_manager = ls
- assert isinstance(ls_manager, LanguageServerManager)
- self._ls_manager: LanguageServerManager = ls_manager
- self.agent = agent
+ self._ls_manager: LanguageServerManager = project.get_language_server_manager_or_raise()
+ self.project = project
def _request_info(self, relative_file_path: str, line: int, column: int, file_buffer: LSPFileBuffer | None = None) -> str | None:
"""Retrieves information (in a sanitized format) about the symbol at the desired location,
@@ -588,16 +578,11 @@ class LanguageServerSymbolRetriever:
return None
return self._request_info(relative_file_path=symbol.relative_path, line=symbol.line, column=symbol.column) # type: ignore[arg-type]
- def _get_symbol_info_budget(self, default_budget: float = 10) -> float:
- """Project -> global -> default"""
- symbol_info_budget = default_budget
- if self.agent is not None:
- symbol_info_budget = self.agent.serena_config.symbol_info_budget
- active_project = self.agent.get_active_project()
- if active_project is not None:
- project_symbol_info_budget = active_project.project_config.symbol_info_budget
- if project_symbol_info_budget is not None:
- symbol_info_budget = project_symbol_info_budget
+ def _get_symbol_info_budget(self) -> float:
+ symbol_info_budget = self.project.serena_config.symbol_info_budget
+ project_symbol_info_budget = self.project.project_config.symbol_info_budget
+ if project_symbol_info_budget is not None:
+ symbol_info_budget = project_symbol_info_budget
return symbol_info_budget
def request_info_for_symbol_batch(
@@ -696,8 +681,8 @@ class LanguageServerSymbolRetriever:
return info_by_symbol
- def get_root_path(self) -> str:
- return self._ls_manager.get_root_path()
+ def can_analyze_file(self, relative_file_path: str) -> bool:
+ return self._ls_manager.has_suitable_ls_for_file(relative_file_path)
def get_language_server(self, relative_path: str) -> SolidLanguageServer:
""":param relative_path: relative path to a file"""
@@ -716,7 +701,16 @@ class LanguageServerSymbolRetriever:
optionally limited to a specific file and filtered by kind.
"""
symbols: list[LanguageServerSymbol] = []
- for lang_server in self._ls_manager.iter_language_servers():
+ if within_relative_path and os.path.isfile(os.path.join(self.project.project_root, within_relative_path)):
+ """
+ For a specific file, use get_language_server to select the best LS for the file type
+ (consistent with get_symbol_overview). This ensures e.g. PHP files are served by the
+ PHP language server rather than being rejected by all LSes via is_ignored_path.
+ """
+ lang_servers: Iterable[SolidLanguageServer] = [self._ls_manager.get_language_server(within_relative_path)]
+ else:
+ lang_servers = self._ls_manager.iter_language_servers()
+ for lang_server in lang_servers:
symbol_roots = lang_server.request_full_symbol_tree(within_relative_path=within_relative_path)
for root in symbol_roots:
symbols.extend(
diff --git a/src/serena/tools/__init__.py b/src/serena/tools/__init__.py
index e6fb4b08..08bb4338 100644
--- a/src/serena/tools/__init__.py
+++ b/src/serena/tools/__init__.py
@@ -7,3 +7,4 @@ from .cmd_tools import *
from .config_tools import *
from .workflow_tools import *
from .jetbrains_tools import *
+from .query_project_tools import *
diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py
index 591f4f01..d4c4e64a 100644
--- a/src/serena/tools/file_tools.py
+++ b/src/serena/tools/file_tools.py
@@ -68,12 +68,12 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit):
if will_overwrite_existing:
self.project.validate_relative_path(relative_path, require_not_ignored=True)
else:
- assert abs_path.is_relative_to(
- self.get_project_root()
- ), f"Cannot create file outside of the project directory, got {relative_path=}"
+ assert abs_path.is_relative_to(self.get_project_root()), (
+ 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=self.project.project_config.encoding)
+ abs_path.write_text(content, encoding=self.project.project_config.encoding, newline=self.project.line_ending.newline_str)
answer = f"File created: {relative_path}."
if will_overwrite_existing:
answer += " Overwrote existing file."
@@ -192,10 +192,8 @@ class ReplaceContentTool(Tool, ToolMarkerCanEdit):
If mode is "regex", the string can contain backreferences to matched groups in the needle regex,
specified using the syntax $!1, $!2, etc. for groups 1, 2, etc.
:param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
- :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
- (and you may retry with a revised, more specific regex).
+ :param allow_multiple_occurrences: whether to allow matching and replacing multiple occurrences.
+ If false and multiple occurrences are found, an error will be returned
"""
return self.replace_content(
relative_path, needle, repl, mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, require_not_ignored=True
diff --git a/src/serena/tools/memory_tools.py b/src/serena/tools/memory_tools.py
index dccf3c16..9ecb0c0c 100644
--- a/src/serena/tools/memory_tools.py
+++ b/src/serena/tools/memory_tools.py
@@ -1,17 +1,20 @@
from typing import Literal
-from serena.tools import ReplaceContentTool, Tool, ToolMarkerCanEdit
+from serena.tools import Tool, ToolMarkerCanEdit
class WriteMemoryTool(Tool, ToolMarkerCanEdit):
"""
- Writes a named memory (for future reference) to Serena's project-specific memory store.
+ 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.
"""
def apply(self, memory_name: str, content: str, max_chars: int = -1) -> str:
"""
- Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
+ Write 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 and can include "/" to organize into topics (e.g., "auth/login/logic").
+ If explicitly instructed, use the "global/" prefix for writing a memory that is shared across projects
+ (e.g., "global/java/style_guide")
:param max_chars: the maximum number of characters to write. By default, determined by the config,
change only if instructed to do so.
@@ -24,66 +27,77 @@ class WriteMemoryTool(Tool, ToolMarkerCanEdit):
f"Content for {memory_name} is too long. Max length is {max_chars} characters. " + "Please make the content shorter."
)
- return self.memories_manager.save_memory(memory_name, content)
+ return self.memories_manager.save_memory(memory_name, content, is_tool_context=True)
class ReadMemoryTool(Tool):
"""
- Reads the memory with the given name from Serena's project-specific memory store.
+ Read the content of a memory file. This tool should only be used if the information
+ is relevant to the current task. You can infer whether the information
+ is relevant from the memory file name.
+ You should not read the same memory file multiple times in the same conversation.
"""
def apply(self, memory_name: str) -> str:
"""
- Read the content of a memory. Should only be used if the information
- is relevant to the current task, with relevance inferred from the memory name.
- You should not read the same memory file multiple times in the same conversation.
+ Reads the contents of a memory. Should only be used if the information
+ is likely to be relevant to the current task, inferring relevance from the memory name.
"""
return self.memories_manager.load_memory(memory_name)
class ListMemoriesTool(Tool):
"""
- Lists memories in Serena's project-specific memory store.
+ List available memories. Any memory can be read using the `read_memory` tool.
"""
def apply(self, topic: str = "") -> str:
"""
- List available memories, optionally filtered by topic.
+ Lists available memories, optionally filtered by topic.
"""
- return self._to_json(self.memories_manager.list_memories(topic))
+ return self._to_json(self.memories_manager.list_memories(topic).to_dict())
class DeleteMemoryTool(Tool, ToolMarkerCanEdit):
"""
- Deletes a memory from Serena's project-specific memory store.
+ Delete a memory file. Should only happen if a user asks for it explicitly,
+ for example by saying that the information retrieved from a memory file is no longer correct
+ or no longer relevant for the project.
"""
def apply(self, memory_name: str) -> str:
"""
Delete a memory, only call if instructed explicitly or permission was granted by the user.
"""
- return self.memories_manager.delete_memory(memory_name)
+ return self.memories_manager.delete_memory(memory_name, is_tool_context=True)
class RenameMemoryTool(Tool, ToolMarkerCanEdit):
"""
- Renames or moves a memory in Serena's project-specific memory store.
+ Renames or moves a memory. Moving between project and global scope is supported
+ (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
"""
def apply(self, old_name: str, new_name: str) -> str:
"""
Rename or move a memory, use "/" in the name to organize into topics.
+ The "global" topic should only be used if explicitly instructed.
"""
- return self.memories_manager.rename_memory(old_name, new_name)
+ return self.memories_manager.move_memory(old_name, new_name, is_tool_context=True)
class EditMemoryTool(Tool, ToolMarkerCanEdit):
+ """
+ Replaces content matching a regular expression in a memory.
+ """
+
def apply(
self,
memory_name: str,
needle: str,
repl: str,
mode: Literal["literal", "regex"],
+ allow_multiple_occurrences: bool = False,
) -> str:
r"""
Replaces content matching a regular expression in a memory.
@@ -95,7 +109,7 @@ class EditMemoryTool(Tool, ToolMarkerCanEdit):
with flags DOTALL and MULTILINE enabled).
:param repl: the replacement string (verbatim).
:param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
+ :param allow_multiple_occurrences: whether to allow matching and replacing multiple occurrences.
+ If false and multiple occurrences are found, an error will be returned.
"""
- replace_content_tool = self.agent.get_tool(ReplaceContentTool)
- rel_path = self.memories_manager.get_memory_file_path(memory_name).relative_to(self.get_project_root())
- return replace_content_tool.replace_content(str(rel_path), needle, repl, mode=mode, require_not_ignored=False)
+ return self.memories_manager.edit_memory(memory_name, needle, repl, mode, allow_multiple_occurrences, is_tool_context=True)
diff --git a/src/serena/tools/query_project_tools.py b/src/serena/tools/query_project_tools.py
new file mode 100644
index 00000000..b675a0aa
--- /dev/null
+++ b/src/serena/tools/query_project_tools.py
@@ -0,0 +1,80 @@
+import json
+
+from serena.config.serena_config import LanguageBackend
+from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClientManager
+from serena.project_server import ProjectServerClient
+from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
+
+
+class ListQueryableProjectsTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject):
+ """
+ Tool for listing all projects that can be queried by the QueryProjectTool.
+ """
+
+ def apply(self, symbol_access: bool = True) -> str:
+ """
+ Lists available projects that can be queried with `query_project_tool`.
+
+ :param symbol_access: whether to return only projects for which symbol access is available. Default: true
+ """
+ # determine relevant projects
+ registered_projects = self.agent.serena_config.projects
+ if symbol_access:
+ backend = self.agent.get_language_backend()
+ if backend.is_jetbrains():
+ # projects with open IDE instances can be queried
+ matched_clients = JetBrainsPluginClientManager().match_clients(registered_projects)
+ relevant_projects = [mc.registered_project for mc in matched_clients]
+ else:
+ # all projects can be queried via ProjectServer (which instantiates projects dynamically)
+ relevant_projects = registered_projects
+ else:
+ relevant_projects = registered_projects
+
+ # return project names, excluding the active project (if any)
+ project_names = [p.project_name for p in relevant_projects]
+ active_project = self.agent.get_active_project()
+ if active_project is not None:
+ project_names = [n for n in project_names if n != active_project.project_name]
+ return self._to_json(project_names)
+
+
+class QueryProjectTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject):
+ """
+ Tool for querying external project information (i.e. information from projects other than the current one),
+ by executing a read-only tool.
+ """
+
+ def apply(self, project_name: str, tool_name: str, tool_params_json: str) -> str:
+ """
+ Queries a project by executing a read-only Serena tool. The tool will be executed in the context of the project.
+ Use this to query information from projects other than the activated project.
+
+ :param project_name: the name of the project to query
+ :param tool_name: the name of the tool to execute in the other project. The tool must be read-only.
+ :param tool_params_json: the parameters to pass to the tool, encoded as a JSON string
+ """
+ tool = self.agent.get_tool_by_name(tool_name)
+ assert tool.is_active(), f"Tool {tool_name} is not active."
+ assert tool.is_readonly(), f"Tool {tool_name} is not read-only and cannot be executed in another project."
+ if self._is_project_server_required(tool):
+ client = ProjectServerClient()
+ return client.query_project(project_name, tool_name, tool_params_json)
+ else:
+ registered_project = self.agent.serena_config.get_registered_project(project_name)
+ assert registered_project is not None, f"Project {project_name} is not registered and cannot be queried."
+ project = registered_project.get_project_instance(self.agent.serena_config)
+ with tool.agent.active_project_context(project):
+ return tool.apply(**json.loads(tool_params_json)) # type: ignore
+
+ def _is_project_server_required(self, tool: Tool) -> bool:
+ match self.agent.get_language_backend():
+ case LanguageBackend.JETBRAINS:
+ return False
+ case LanguageBackend.LSP:
+ # Note: As long as only read-only tools are considered, only symbolic tools require the project server.
+ # But if we were to allow non-read-only tools, then tools using a CodeEditor also indirectly require language servers.
+ assert tool.is_readonly()
+ return tool.is_symbolic()
+ case _:
+ raise NotImplementedError
diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py
index 07d6ad8c..148611f5 100644
--- a/src/serena/tools/symbol_tools.py
+++ b/src/serena/tools/symbol_tools.py
@@ -68,6 +68,10 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead):
raise FileNotFoundError(f"File or directory {relative_path} does not exist in the project.")
if os.path.isdir(file_path):
raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ")
+ if not symbol_retriever.can_analyze_file(relative_path):
+ raise ValueError(
+ f"Cannot extract symbols from file {relative_path}. Active languages: {[l.value for l in self.agent.get_active_lsp_languages()]}"
+ )
symbols = symbol_retriever.get_symbol_overview(relative_path)[relative_path]
diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py
index 9fb1a393..503ab341 100644
--- a/src/serena/tools/tools_base.py
+++ b/src/serena/tools/tools_base.py
@@ -12,6 +12,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.config.serena_config import LanguageBackend
from serena.project import MemoriesManager, Project
from serena.prompt_factory import PromptFactory
from serena.util.class_decorators import singleton
@@ -36,7 +37,7 @@ class Component(ABC):
"""
:return: the root directory of the active project, raises a ValueError if no active project configuration is set
"""
- return self.agent.get_project_root()
+ return self.project.project_root
@property
def prompt_factory(self) -> PromptFactory:
@@ -49,10 +50,8 @@ class Component(ABC):
def create_language_server_symbol_retriever(self) -> "LanguageServerSymbolRetriever":
from serena.symbol import LanguageServerSymbolRetriever
- if not self.agent.is_using_language_server():
- raise Exception("Cannot create LanguageServerSymbolRetriever; agent is not in language server mode.")
- language_server_manager = self.agent.get_language_server_manager_or_raise()
- return LanguageServerSymbolRetriever(language_server_manager, agent=self.agent)
+ assert self.agent.get_language_backend().is_lsp(), "Language server symbol retriever can only be created for LSP language backend"
+ return LanguageServerSymbolRetriever(self.project)
@property
def project(self) -> Project:
@@ -61,10 +60,13 @@ class Component(ABC):
def create_code_editor(self) -> "CodeEditor":
from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor
- if self.agent.is_using_language_server():
- return LanguageServerCodeEditor(self.create_language_server_symbol_retriever(), agent=self.agent)
- else:
- return JetBrainsCodeEditor(project=self.project, agent=self.agent)
+ match self.agent.get_language_backend():
+ case LanguageBackend.LSP:
+ return LanguageServerCodeEditor(self.create_language_server_symbol_retriever())
+ case LanguageBackend.JETBRAINS:
+ return JetBrainsCodeEditor(project=self.project)
+ case _:
+ raise ValueError
class ToolMarker:
@@ -234,6 +236,12 @@ class Tool(Component):
def is_active(self) -> bool:
return self.agent.tool_is_active(self.get_name())
+ def is_readonly(self) -> bool:
+ return not self.can_edit()
+
+ def is_symbolic(self) -> bool:
+ return issubclass(self.__class__, ToolMarkerSymbolicRead) or issubclass(self.__class__, ToolMarkerSymbolicEdit)
+
def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, mcp_ctx: Context | None = None, **kwargs) -> str: # type: ignore
"""
Applies the tool with logging and exception handling, using the given keyword arguments
@@ -374,6 +382,13 @@ class RegisteredTool:
is_optional: bool
tool_name: str
+ @property
+ def class_docstring(self) -> str:
+ """
+ :return: the tool description (high-level class docstring)
+ """
+ return self.tool_class.get_tool_description()
+
tool_packages = ["serena.tools"]
@@ -382,7 +397,8 @@ tool_packages = ["serena.tools"]
class ToolRegistry:
def __init__(self) -> None:
self._tool_dict: dict[str, RegisteredTool] = {}
- for cls in iter_subclasses(Tool):
+ inclusion_predicate = lambda c: "apply" in c.__dict__ # include only concrete tool classes that implement apply
+ for cls in iter_subclasses(Tool, inclusion_predicate=inclusion_predicate):
if not any(cls.__module__.startswith(pkg) for pkg in tool_packages):
continue
is_optional = issubclass(cls, ToolMarkerOptional)
@@ -391,7 +407,24 @@ class ToolRegistry:
raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.")
self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name)
+ def get_registered_tools_by_module(self) -> dict[str, list[RegisteredTool]]:
+ """
+ :return: the registered tools grouped by their module (ordered alphabetically by module and tool name)
+ """
+ module_dict: dict[str, list[RegisteredTool]] = {}
+ for tool in self._tool_dict.values():
+ module = tool.tool_class.__module__
+ if module not in module_dict:
+ module_dict[module] = []
+ module_dict[module].append(tool)
+ sorted_module_dict = {}
+ for module in sorted(module_dict.keys()):
+ sorted_module_dict[module] = sorted(module_dict[module], key=lambda t: t.tool_name)
+ return sorted_module_dict
+
def get_tool_class_by_name(self, tool_name: str) -> type[Tool]:
+ if tool_name not in self._tool_dict:
+ raise ValueError(f"Tool named '{tool_name}' not found.")
return self._tool_dict[tool_name].tool_class
def get_all_tool_classes(self) -> list[type[Tool]]:
diff --git a/src/serena/tools/workflow_tools.py b/src/serena/tools/workflow_tools.py
index 9528f565..158170a4 100644
--- a/src/serena/tools/workflow_tools.py
+++ b/src/serena/tools/workflow_tools.py
@@ -2,7 +2,6 @@
Tools supporting the general workflow of the agent
"""
-import json
import platform
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
@@ -18,23 +17,21 @@ class CheckOnboardingPerformedTool(Tool):
Checks whether project onboarding was already performed.
You should always call this tool before beginning to actually work on the project/after activating a project.
"""
- from .memory_tools import ListMemoriesTool
-
- list_memories_tool = self.agent.get_tool(ListMemoriesTool)
- memories = json.loads(list_memories_tool.apply())
- if len(memories) == 0:
- return (
+ project_memories = self.memories_manager.list_project_memories()
+ if len(project_memories) == 0:
+ msg = (
"Onboarding not performed yet (no memories available). "
- + "You should perform onboarding by calling the `onboarding` tool before proceeding with the task."
+ "You should perform onboarding by calling the `onboarding` tool before proceeding with the task. "
)
else:
- return f"""The onboarding was already performed, below is the list of available memories.
- Do not read them immediately, just remember that they exist and that you can read them later, if it is necessary
- for the current task.
- Some memories may be based on previous conversations, others may be general for the current project.
- You should be able to tell which one you need based on the name of the memory.
-
- {memories}"""
+ # Not reporting the list of memories here, as they were already reported at project activation
+ # (with the system prompt if the project was activated at startup)
+ msg = (
+ f"Onboarding was already performed: {len(project_memories)} project memories are available. "
+ "Consider reading memories if they appear relevant to the task at hand."
+ )
+ msg += " If you have not read the 'Serena Instructions Manual', do so now."
+ return msg
class OnboardingTool(Tool):
diff --git a/src/serena/util/dotnet.py b/src/serena/util/dotnet.py
new file mode 100644
index 00000000..99b34526
--- /dev/null
+++ b/src/serena/util/dotnet.py
@@ -0,0 +1,156 @@
+import logging
+import platform
+import re
+import shutil
+import subprocess
+import urllib
+from pathlib import Path
+
+from serena.util.version import Version
+from solidlsp.ls_exceptions import SolidLSPException
+
+log = logging.getLogger(__name__)
+
+
+class DotNETUtil:
+ def __init__(self, required_version: str, allow_higher_version: bool = True):
+ """
+ :param required_version: the required .NET runtime version specified as a string (e.g. "10.0" for .NET 10.0)
+ :param allow_higher_version: whether to allow higher versions than the required version
+ """
+ self._system_dotnet = shutil.which("dotnet")
+ self._required_version_str = required_version
+ self._required_version_components = [int(c) for c in required_version.split(".")]
+ self._allow_higher_version = allow_higher_version
+ self._installed_versions = self._determine_installed_versions()
+
+ def _determine_installed_versions(self) -> list[Version]:
+ if self._system_dotnet:
+ try:
+ result = subprocess.run([self._system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
+ version_strings = re.findall(r"Microsoft.NETCore.App\s+([^\s]+)", result.stdout)
+ log.info("Installed .NET runtime versions: %s", version_strings)
+ return [Version(v) for v in version_strings]
+ except:
+ log.warning("Failed to run 'dotnet --list-runtimes' to check .NET version; assuming no installed .NET versions")
+ return []
+ else:
+ log.info("Found no `dotnet` on system PATH; assuming no installed .NET versions")
+ return []
+
+ def is_required_version_available(self) -> bool:
+ """
+ Checks whether the required .NET runtime version is installed and raises an exception if not.
+
+ :param required_version_components: the required .NET runtime version specified as a list of integers representing the version components (e.g., [6, 1] for .NET 6.1)
+ :param allow_higher_version: whether to allow higher versions than the required version (e.g., if True, .NET 7.0 would satisfy a requirement of .NET 6.1)
+ """
+ required_version_str = ".".join(str(c) for c in self._required_version_components)
+ for v in self._installed_versions:
+ if self._allow_higher_version:
+ if v.is_at_least(*self._required_version_components):
+ log.info(f"Found installed .NET runtime version {v} which satisfies requirement of {required_version_str} or higher")
+ return True
+ else:
+ if v.is_equal(*self._required_version_components):
+ log.info(f"Found installed .NET runtime version {v} which satisfies requirement of {required_version_str}")
+ return True
+ return False
+
+ def get_dotnet_path_or_raise(self) -> str:
+ """
+ Returns the path to the dotnet executable if the required .NET runtime version is available, otherwise raises an exception.
+ """
+ if not self.is_required_version_available():
+ raise SolidLSPException(
+ f"Required .NET runtime version {self._required_version_str} not found "
+ f"(installed versions: {self._installed_versions}). "
+ "Please install the required .NET runtime version from https://dotnet.microsoft.com/en-us/download/dotnet "
+ "and ensure that `dotnet` is on the system PATH."
+ )
+ assert self._system_dotnet is not None
+ return self._system_dotnet
+
+ @staticmethod
+ def install_dotnet_with_script(version: str, base_path: str) -> str:
+ """
+ Install .NET runtime using Microsoft's official installation script.
+
+ NOTE: This method is unreliable and therefore currently unused. It is kept for reference.
+
+ :version: the version to install as a string (e.g. "10.0")
+ :return: the path to the dotnet executable.
+ """
+ dotnet_dir = Path(base_path) / f"dotnet-runtime-{version}"
+
+ # Determine binary name based on platform
+ is_windows = platform.system().lower() == "windows"
+ dotnet_exe = dotnet_dir / ("dotnet.exe" if is_windows else "dotnet")
+
+ if dotnet_exe.exists():
+ log.info(f"Using cached .NET {version} runtime from {dotnet_exe}")
+ return str(dotnet_exe)
+
+ # Download and run install script
+ log.info(f"Installing .NET {version} runtime using official Microsoft install script...")
+ dotnet_dir.mkdir(parents=True, exist_ok=True)
+
+ try:
+ if is_windows:
+ # PowerShell script for Windows
+ script_url = "https://dot.net/v1/dotnet-install.ps1"
+ script_path = dotnet_dir / "dotnet-install.ps1"
+ urllib.request.urlretrieve(script_url, script_path)
+
+ cmd = [
+ "pwsh",
+ "-NoProfile",
+ "-ExecutionPolicy",
+ "Bypass",
+ "-File",
+ str(script_path),
+ "-Version",
+ version,
+ "-InstallDir",
+ str(dotnet_dir),
+ "-Runtime",
+ "dotnet",
+ "-NoPath",
+ ]
+ else:
+ # Bash script for Linux/macOS
+ script_url = "https://dot.net/v1/dotnet-install.sh"
+ script_path = dotnet_dir / "dotnet-install.sh"
+ urllib.request.urlretrieve(script_url, script_path)
+ script_path.chmod(0o755)
+
+ cmd = [
+ "bash",
+ str(script_path),
+ "--version",
+ version,
+ "--install-dir",
+ str(dotnet_dir),
+ "--runtime",
+ "dotnet",
+ "--no-path",
+ ]
+
+ # Run the install script
+ log.info("Running .NET install script: %s", cmd)
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
+ log.debug(f"Install script output: {result.stdout}")
+
+ if not dotnet_exe.exists():
+ raise SolidLSPException(f"dotnet executable not found at {dotnet_exe} after installation")
+
+ log.info(f"Successfully installed .NET {version} runtime to {dotnet_exe}")
+ return str(dotnet_exe)
+
+ except subprocess.CalledProcessError as e:
+ raise SolidLSPException(f"Failed to install .NET {version} runtime using install script: {e.stderr if e.stderr else e}") from e
+ except Exception as e:
+ message = f"Failed to install .NET {version} runtime: {e}"
+ if is_windows and isinstance(e, FileNotFoundError):
+ message += "; pwsh, i.e. PowerShell 7+, is required to install .NET runtime. Make sure pwsh is available on your system."
+ raise SolidLSPException(message) from e
diff --git a/src/serena/util/inspection.py b/src/serena/util/inspection.py
index c67cc1f3..a0e3e96a 100644
--- a/src/serena/util/inspection.py
+++ b/src/serena/util/inspection.py
@@ -1,6 +1,6 @@
import logging
import os
-from collections.abc import Generator
+from collections.abc import Callable, Iterator
from typing import TypeVar
from serena.util.file_system import find_all_non_ignored_files
@@ -11,12 +11,20 @@ T = TypeVar("T")
log = logging.getLogger(__name__)
-def iter_subclasses(cls: type[T], recursive: bool = True) -> Generator[type[T], None, None]:
- """Iterate over all subclasses of a class. If recursive is True, also iterate over all subclasses of all subclasses."""
+def iter_subclasses(
+ cls: type[T], recursive: bool = True, inclusion_predicate: Callable[[type[T]], bool] = lambda t: True
+) -> Iterator[type[T]]:
+ """Iterate over all subclasses of a class.
+
+ :param cls: The class whose subclasses to iterate over.
+ :param recursive: If True, also iterate over all subclasses of all subclasses.
+ :param inclusion_predicate: a predicate function to decide whether to include a subclass in the result
+ """
for subclass in cls.__subclasses__():
- yield subclass
+ if inclusion_predicate(subclass):
+ yield subclass
if recursive:
- yield from iter_subclasses(subclass, recursive)
+ yield from iter_subclasses(subclass, recursive, inclusion_predicate)
def determine_programming_language_composition(repo_path: str) -> dict[Language, float]:
diff --git a/src/serena/util/shell.py b/src/serena/util/shell.py
index 3b310852..ce10cdbf 100644
--- a/src/serena/util/shell.py
+++ b/src/serena/util/shell.py
@@ -43,7 +43,9 @@ def execute_shell_command(command: str, cwd: str | None = None, capture_stderr:
def subprocess_check_output(args: list[str], encoding: str = "utf-8", strip: bool = True, timeout: float | None = None) -> str:
- output = subprocess.check_output(args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), **subprocess_kwargs()).decode(encoding) # type: ignore
+ output = subprocess.check_output(
+ args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), **subprocess_kwargs()
+ ).decode(encoding) # type: ignore
if strip:
output = output.strip()
return output
diff --git a/src/serena/util/thread.py b/src/serena/util/thread.py
index 7b779187..6d21ebed 100644
--- a/src/serena/util/thread.py
+++ b/src/serena/util/thread.py
@@ -16,7 +16,6 @@ T = TypeVar("T")
class ExecutionResult(Generic[T], ToStringMixin):
-
class Status(Enum):
SUCCESS = "success"
TIMEOUT = "timeout"
diff --git a/src/serena/util/version.py b/src/serena/util/version.py
index d8d8381f..142d4356 100644
--- a/src/serena/util/version.py
+++ b/src/serena/util/version.py
@@ -23,7 +23,7 @@ class Version:
self.version_string = version_string
self.components = self._get_version_components(version_string)
- def __str__(self) -> str:
+ def __repr__(self) -> str:
return self.version_string
@staticmethod
diff --git a/src/serena/util/yaml.py b/src/serena/util/yaml.py
index 32a8f364..e815dd3f 100644
--- a/src/serena/util/yaml.py
+++ b/src/serena/util/yaml.py
@@ -68,7 +68,9 @@ def load_yaml(path: str, comment_normalisation: YamlCommentNormalisation = YamlC
"""
with open(path, encoding=SERENA_FILE_ENCODING) as f:
yaml = _create_yaml(preserve_comments=True)
- commented_map: CommentedMap = yaml.load(f)
+ commented_map: CommentedMap | None = yaml.load(f)
+ if commented_map is None: # ruamel returns None for empty documents, but we want an empty CommentedMap
+ commented_map = CommentedMap()
normalise_yaml_comments(commented_map, comment_normalisation)
return commented_map
diff --git a/src/solidlsp/language_servers/ansible_language_server.py b/src/solidlsp/language_servers/ansible_language_server.py
new file mode 100644
index 00000000..7c2f88a1
--- /dev/null
+++ b/src/solidlsp/language_servers/ansible_language_server.py
@@ -0,0 +1,318 @@
+"""
+Provides Ansible specific instantiation of the LanguageServer class using ansible-language-server.
+Contains various configurations and settings specific to Ansible YAML files (playbooks, roles, etc.).
+"""
+
+import fnmatch
+import logging
+import os
+import pathlib
+import shutil
+from typing import Any, ClassVar
+
+from overrides import override
+
+from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
+from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
+from solidlsp.ls_config import LanguageServerConfig
+from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
+from solidlsp.settings import SolidLSPSettings
+
+log = logging.getLogger(__name__)
+
+
+def _deep_merge(base: dict, override: dict) -> None:
+ """Recursively merge *override* into *base*, modifying *base* in-place."""
+ for key, value in override.items():
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
+ _deep_merge(base[key], value)
+ else:
+ base[key] = value
+
+
+class AnsibleLanguageServer(SolidLanguageServer):
+ """Provides Ansible specific instantiation of the LanguageServer class using ansible-language-server.
+
+ Contains various configurations and settings specific to Ansible YAML files
+ (playbooks, roles, inventories, etc.).
+
+ Supported ``ls_specific_settings`` keys (via ``serena_config.yml``):
+
+ * ``ls_path`` (str) — path to the ansible-language-server executable
+ (handled by the base class).
+ * ``ansible_path`` (str, default ``"ansible"``) — path to the ``ansible`` executable.
+ * ``python_interpreter_path`` (str, default ``"python3"``) — path to the Python interpreter.
+ * ``python_activation_script`` (str, default ``""``) — virtualenv activation script.
+ * ``lint_enabled`` (bool, default ``False``) — enable ansible-lint
+ (requires a separate installation of ``ansible-lint``).
+ * ``lint_path`` (str, default ``"ansible-lint"``) — path to ``ansible-lint``.
+ * ``ansible_settings`` (dict) — full settings dict, deep-merged on top of defaults.
+ The structure mirrors the Ansible Language Server settings
+ (``ansible.*``, ``python.*``, ``validation.*``, ``completion.*``,
+ ``executionEnvironment.*``).
+ """
+
+ # directory names that signal ansible content at ANY nesting level
+ _ANSIBLE_DIR_NAMES: ClassVar[set[str]] = {
+ "roles",
+ "playbooks",
+ "tasks",
+ "handlers",
+ "group_vars",
+ "host_vars",
+ "inventory",
+ "inventories",
+ "defaults",
+ "vars",
+ "meta",
+ }
+
+ # filename patterns handled by ansible LS regardless of path
+ _ANSIBLE_FILENAME_PATTERNS: ClassVar[list[str]] = [
+ "playbook*.yml",
+ "playbook*.yaml",
+ "site.yml",
+ "site.yaml",
+ "requirements.yml",
+ "requirements.yaml",
+ ]
+
+ @staticmethod
+ def _is_ansible_path(relative_path: str) -> bool:
+ """Check if a file is in an ansible-specific location.
+
+ Matches if ANY component of the path is an ansible-specific
+ directory name (e.g. ``roles``, ``tasks``, ``group_vars``),
+ or if the filename matches an ansible-specific pattern.
+ This works regardless of nesting depth:
+ ``project/deploy/roles/web/tasks/main.yml`` matches on both
+ ``roles`` and ``tasks``.
+
+ :param relative_path: path relative to the repository root
+ :return: True if the path is an ansible-specific location
+ """
+ normalized = relative_path.replace("\\", "/")
+ parts = normalized.split("/")
+
+ # check if any directory component is ansible-specific
+ dir_parts = parts[:-1]
+ for part in dir_parts:
+ if part in AnsibleLanguageServer._ANSIBLE_DIR_NAMES:
+ return True
+
+ # check filename patterns (e.g. playbook.yml, site.yaml)
+ filename = parts[-1]
+ for pattern in AnsibleLanguageServer._ANSIBLE_FILENAME_PATTERNS:
+ if fnmatch.fnmatch(filename, pattern):
+ return True
+
+ return False
+
+ @override
+ def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool:
+ # standard ignore rules (extension, gitignore, etc.)
+ if super().is_ignored_path(relative_path, ignore_unsupported_files):
+ return True
+
+ # for yml/yaml files, check if they are in ansible-specific paths
+ if relative_path.endswith((".yml", ".yaml")):
+ if not self._is_ansible_path(relative_path):
+ return True
+
+ return False
+
+ @staticmethod
+ def _determine_log_level(line: str) -> int:
+ """Classify ansible-language-server stderr output to avoid false-positive errors."""
+ line_lower = line.lower()
+
+ if any(
+ [
+ "ansible is not installed" in line_lower,
+ "ansible-lint" in line_lower and "not found" in line_lower,
+ "cannot find module" in line_lower,
+ ]
+ ):
+ return logging.DEBUG
+
+ return SolidLanguageServer._determine_log_level(line)
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
+ """Creates an AnsibleLanguageServer instance.
+
+ This class is not meant to be instantiated directly.
+ Use ``SolidLanguageServer.create()`` instead.
+ """
+ super().__init__(
+ config,
+ repository_root_path,
+ None,
+ "ansible",
+ solidlsp_settings,
+ )
+
+ def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
+ return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
+
+ class DependencyProvider(LanguageServerDependencyProviderSinglePath):
+ def _get_or_install_core_dependency(self) -> str:
+ """Setup runtime dependencies for Ansible Language Server and return the path to the executable."""
+ # verify both node and npm are installed
+ is_node_installed = shutil.which("node") is not None
+ assert is_node_installed, "node is not installed or isn't in PATH. Please install Node.js and try again."
+ is_npm_installed = shutil.which("npm") is not None
+ assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
+
+ deps = RuntimeDependencyCollection(
+ [
+ RuntimeDependency(
+ id="ansible-language-server",
+ description="Ansible Language Server (@ansible/ansible-language-server)",
+ command="npm install --prefix ./ @ansible/ansible-language-server@1.2.3",
+ platform_id="any",
+ ),
+ ]
+ )
+
+ # install ansible-language-server if not already installed
+ ansible_ls_dir = os.path.join(self._ls_resources_dir, "ansible-lsp")
+ ansible_executable_path = os.path.join(ansible_ls_dir, "node_modules", ".bin", "ansible-language-server")
+
+ # handle Windows executable extension
+ if os.name == "nt":
+ ansible_executable_path += ".cmd"
+
+ if not os.path.exists(ansible_executable_path):
+ log.info(f"Ansible Language Server executable not found at {ansible_executable_path}. Installing...")
+ deps.install(ansible_ls_dir)
+ log.info("Ansible Language Server dependencies installed successfully")
+
+ if not os.path.exists(ansible_executable_path):
+ raise FileNotFoundError(
+ f"ansible-language-server executable not found at {ansible_executable_path}, "
+ "something went wrong with the installation."
+ )
+
+ return ansible_executable_path
+
+ def _create_launch_command(self, core_path: str) -> list[str]:
+ return [core_path, "--stdio"]
+
+ def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
+ """Returns the initialize params for the Ansible Language Server.
+
+ Reads shortcut keys and the ``ansible_settings`` dict from ``_custom_settings``
+ to build ``initializationOptions``.
+ """
+ root_uri = pathlib.Path(repository_absolute_path).as_uri()
+
+ # default ansible settings, populated from shortcut keys
+ ansible_settings: dict[str, Any] = {
+ "ansible": {
+ "path": self._custom_settings.get("ansible_path", "ansible"),
+ "useFullyQualifiedCollectionNames": True,
+ },
+ "python": {
+ "interpreterPath": self._custom_settings.get("python_interpreter_path", "python3"),
+ "activationScript": self._custom_settings.get("python_activation_script", ""),
+ },
+ "validation": {
+ "enabled": True,
+ "lint": {
+ "enabled": self._custom_settings.get("lint_enabled", False),
+ "path": self._custom_settings.get("lint_path", "ansible-lint"),
+ },
+ },
+ "completion": {
+ "provideRedirectModules": True,
+ "provideModuleOptionAliases": True,
+ },
+ "executionEnvironment": {"enabled": False},
+ }
+
+ # full override via ansible_settings dict for advanced configuration
+ user_settings = self._custom_settings.settings.get("ansible_settings")
+ if user_settings:
+ if not isinstance(user_settings, dict):
+ raise TypeError(
+ f"ansible_settings must be a dict, got {type(user_settings).__name__}. "
+ "Expected structure matching Ansible LS settings: "
+ "{'ansible': {...}, 'python': {...}, 'validation': {...}, ...}"
+ )
+ _deep_merge(ansible_settings, user_settings)
+
+ initialize_params = {
+ "locale": "en",
+ "capabilities": {
+ "textDocument": {
+ "synchronization": {"didSave": True, "dynamicRegistration": True},
+ "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
+ "definition": {"dynamicRegistration": True},
+ "references": {"dynamicRegistration": True},
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ "symbolKind": {"valueSet": list(range(1, 27))},
+ },
+ "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
+ "codeAction": {"dynamicRegistration": True},
+ },
+ "workspace": {
+ "workspaceFolders": True,
+ "didChangeConfiguration": {"dynamicRegistration": True},
+ "symbol": {"dynamicRegistration": True},
+ },
+ },
+ "processId": os.getpid(),
+ "rootPath": repository_absolute_path,
+ "rootUri": root_uri,
+ "workspaceFolders": [
+ {
+ "uri": root_uri,
+ "name": os.path.basename(repository_absolute_path),
+ }
+ ],
+ "initializationOptions": {"ansible": ansible_settings},
+ }
+ return initialize_params # type: ignore
+
+ def _start_server(self) -> None:
+ """Starts the Ansible Language Server, waits for the server to be ready."""
+
+ def register_capability_handler(params: Any) -> None:
+ return
+
+ def show_message_request_handler(params: Any) -> None:
+ """Handle ``window/showMessageRequest`` by returning ``null``.
+
+ Per the LSP spec, returning ``null`` means no action was selected.
+ Without this handler the client replies with ``MethodNotFound``,
+ which the ansible LS treats as fatal.
+ """
+ log.info(f"LSP: window/showMessageRequest (dismissed): {params.get('message', params)}")
+ return
+
+ def do_nothing(params: Any) -> None:
+ return
+
+ def window_log_message(msg: dict) -> None:
+ log.info(f"LSP: window/logMessage: {msg}")
+
+ self.server.on_request("client/registerCapability", register_capability_handler)
+ self.server.on_request("window/showMessageRequest", show_message_request_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)
+
+ log.info("Starting Ansible server process")
+ self.server.start()
+ initialize_params = self._get_initialize_params(self.repository_root_path)
+
+ log.info("Sending initialize request from LSP client to LSP server and awaiting response")
+ init_response = self.server.send.initialize(initialize_params)
+ log.debug(f"Received initialize response from Ansible server: {init_response}")
+
+ log.debug(f"Ansible server capabilities: {list(init_response['capabilities'].keys())}")
+
+ self.server.notify.initialized({})
+ log.info("Ansible server initialization complete")
diff --git a/src/solidlsp/language_servers/ccls_language_server.py b/src/solidlsp/language_servers/ccls_language_server.py
index ade8aaf4..b9897172 100644
--- a/src/solidlsp/language_servers/ccls_language_server.py
+++ b/src/solidlsp/language_servers/ccls_language_server.py
@@ -118,7 +118,7 @@ class CCLS(SolidLanguageServer):
"workspaceFolders": [
{
"uri": root_uri,
- "name": "$name",
+ "name": os.path.basename(repository_absolute_path),
}
],
# ccls supports initializationOptions but none are required for basic functionality
diff --git a/src/solidlsp/language_servers/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server.py
index f0929f79..37aa6d70 100644
--- a/src/solidlsp/language_servers/clangd_language_server.py
+++ b/src/solidlsp/language_servers/clangd_language_server.py
@@ -228,7 +228,7 @@ class ClangdLanguageServer(SolidLanguageServer):
"workspaceFolders": [
{
"uri": root_uri,
- "name": "$name",
+ "name": os.path.basename(repository_absolute_path),
}
],
}
diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py
index ae9d9264..28621bb2 100644
--- a/src/solidlsp/language_servers/csharp_language_server.py
+++ b/src/solidlsp/language_servers/csharp_language_server.py
@@ -6,7 +6,6 @@ import logging
import os
import platform
import shutil
-import subprocess
import threading
import urllib.request
from collections.abc import Iterable
@@ -15,6 +14,7 @@ from typing import Any, cast
from overrides import override
+from serena.util.dotnet import DotNETUtil
from solidlsp.ls import DocumentSymbols, LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
@@ -366,21 +366,7 @@ class CSharpLanguageServer(SolidLanguageServer):
def _ensure_dotnet_runtime(self) -> str:
"""Ensure .NET runtime is available and return the dotnet executable path."""
- # Check if dotnet is already available on the system
- system_dotnet = shutil.which("dotnet")
- if system_dotnet:
- # Check if it's .NET 10 or compatible
- try:
- result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
- # Accept .NET 10 or higher (10.x, 11.x, etc.)
- if any(f"Microsoft.NETCore.App {v}." in result.stdout for v in range(10, 20)):
- log.info("Found system .NET 10+ runtime")
- return system_dotnet
- except subprocess.CalledProcessError:
- pass
-
- # Install .NET 10 runtime using Microsoft's install script
- return self._install_dotnet_with_script()
+ return DotNETUtil("10.0", allow_higher_version=True).get_dotnet_path_or_raise()
def _ensure_language_server(self, lang_server_dep: RuntimeDependency) -> str:
"""Ensure language server is available and return the DLL path."""
@@ -475,89 +461,6 @@ class CSharpLanguageServer(SolidLanguageServer):
except Exception as e:
raise SolidLSPException(f"Failed to download package {package_name} version {package_version} from NuGet.org: {e}") from e
- def _install_dotnet_with_script(self, version: str = "10.0") -> str:
- """
- Install .NET runtime using Microsoft's official install script.
- Returns the path to the dotnet executable.
- """
- dotnet_dir = Path(self._ls_resources_dir) / f"dotnet-runtime-{version}"
-
- # Determine binary name based on platform
- is_windows = platform.system().lower() == "windows"
- dotnet_exe = dotnet_dir / ("dotnet.exe" if is_windows else "dotnet")
-
- if dotnet_exe.exists():
- log.info(f"Using cached .NET {version} runtime from {dotnet_exe}")
- return str(dotnet_exe)
-
- # Download and run install script
- log.info(f"Installing .NET {version} runtime using official Microsoft install script...")
- dotnet_dir.mkdir(parents=True, exist_ok=True)
-
- try:
- if is_windows:
- # PowerShell script for Windows
- script_url = "https://dot.net/v1/dotnet-install.ps1"
- script_path = dotnet_dir / "dotnet-install.ps1"
- urllib.request.urlretrieve(script_url, script_path)
-
- cmd = [
- "pwsh",
- "-NoProfile",
- "-ExecutionPolicy",
- "Bypass",
- "-File",
- str(script_path),
- "-Version",
- version,
- "-InstallDir",
- str(dotnet_dir),
- "-Runtime",
- "dotnet",
- "-NoPath",
- ]
- else:
- # Bash script for Linux/macOS
- script_url = "https://dot.net/v1/dotnet-install.sh"
- script_path = dotnet_dir / "dotnet-install.sh"
- urllib.request.urlretrieve(script_url, script_path)
- script_path.chmod(0o755)
-
- cmd = [
- "bash",
- str(script_path),
- "--version",
- version,
- "--install-dir",
- str(dotnet_dir),
- "--runtime",
- "dotnet",
- "--no-path",
- ]
-
- # Run the install script
- log.info("Running .NET install script: %s", cmd)
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
- log.debug(f"Install script output: {result.stdout}")
-
- if not dotnet_exe.exists():
- raise SolidLSPException(f"dotnet executable not found at {dotnet_exe} after installation")
-
- log.info(f"Successfully installed .NET {version} runtime to {dotnet_exe}")
- return str(dotnet_exe)
-
- except subprocess.CalledProcessError as e:
- raise SolidLSPException(
- f"Failed to install .NET {version} runtime using install script: {e.stderr if e.stderr else e}"
- ) from e
- except Exception as e:
- message = f"Failed to install .NET {version} runtime: {e}"
- if is_windows and isinstance(e, FileNotFoundError):
- message += (
- "; pwsh, i.e. PowerShell 7+, is required to install .NET runtime. Make sure pwsh is available on your system."
- )
- raise SolidLSPException(message) from e
-
def _get_initialize_params(self) -> InitializeParams:
"""
Returns the initialize params for the Microsoft.CodeAnalysis.LanguageServer.
diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py
index 67a23c3b..c44bb791 100644
--- a/src/solidlsp/language_servers/eclipse_jdtls.py
+++ b/src/solidlsp/language_servers/eclipse_jdtls.py
@@ -3,12 +3,12 @@ Provides Java specific instantiation of the LanguageServer class. Contains vario
"""
import dataclasses
+import hashlib
import logging
import os
import pathlib
import shutil
import threading
-import uuid
from pathlib import PurePath
from time import sleep
from typing import cast
@@ -276,13 +276,16 @@ class EclipseJDTLS(SolidLanguageServer):
)
def create_launch_command(self) -> list[str]:
- # ws_dir is the workspace directory for the EclipseJDTLS server
+ # ws_dir is the workspace directory for the EclipseJDTLS server.
+ # Use a deterministic hash of the project path so the workspace
+ # (and its cached index) can be reused across restarts.
+ project_hash = hashlib.md5(self._repository_root_path.encode()).hexdigest()
ws_dir = str(
PurePath(
self._solidlsp_settings.ls_resources_dir,
"EclipseJDTLS",
"workspaces",
- uuid.uuid4().hex,
+ project_hash,
)
)
diff --git a/src/solidlsp/language_servers/erlang_language_server.py b/src/solidlsp/language_servers/erlang_language_server.py
index a36cb799..ebf9008c 100644
--- a/src/solidlsp/language_servers/erlang_language_server.py
+++ b/src/solidlsp/language_servers/erlang_language_server.py
@@ -2,6 +2,7 @@
import logging
import os
+import pathlib
import shutil
import subprocess
import threading
@@ -134,7 +135,7 @@ class ErlangLanguageServer(SolidLanguageServer):
initialize_params = {
"processId": None,
"rootPath": self.repository_root_path,
- "rootUri": f"file://{self.repository_root_path}",
+ "rootUri": pathlib.Path(self.repository_root_path).as_uri(),
"capabilities": {
"textDocument": {
"synchronization": {"didSave": True},
diff --git a/src/solidlsp/language_servers/fsharp_language_server.py b/src/solidlsp/language_servers/fsharp_language_server.py
index e4ee3e2c..66e472b5 100644
--- a/src/solidlsp/language_servers/fsharp_language_server.py
+++ b/src/solidlsp/language_servers/fsharp_language_server.py
@@ -11,6 +11,7 @@ from pathlib import Path
from overrides import override
+from serena.util.dotnet import DotNETUtil
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
@@ -61,21 +62,7 @@ class FSharpLanguageServer(SolidLanguageServer):
"""
Setup runtime dependencies for F# Language Server and return the command to start the server.
"""
- # First check if .NET SDK is installed
- dotnet_exe = shutil.which("dotnet")
- if not dotnet_exe:
- raise RuntimeError(
- ".NET SDK is not installed or not in PATH. Please install .NET SDK 8.0 or later and ensure 'dotnet' is in your PATH."
- )
-
- # Verify dotnet version
- import subprocess
-
- try:
- result = subprocess.run([dotnet_exe, "--version"], capture_output=True, text=True, check=True)
- log.info(f"Found .NET SDK version: {result.stdout.strip()}")
- except subprocess.CalledProcessError:
- raise RuntimeError("Failed to get .NET SDK version. Please ensure .NET SDK is properly installed.")
+ dotnet_exe = DotNETUtil("8.0", allow_higher_version=True).get_dotnet_path_or_raise()
RuntimeDependencyCollection(
[
@@ -179,7 +166,7 @@ class FSharpLanguageServer(SolidLanguageServer):
"documentHighlight": {"dynamicRegistration": True},
"documentSymbol": {
"dynamicRegistration": True,
- "symbolKind": {"valueSet": list(range(1, 26))}, # All SymbolKind values
+ "symbolKind": {"valueSet": list(range(1, 27))}, # All SymbolKind values (1-26)
"hierarchicalDocumentSymbolSupport": True,
},
"codeAction": {
diff --git a/src/solidlsp/language_servers/groovy_language_server.py b/src/solidlsp/language_servers/groovy_language_server.py
index 6a2dca09..81a4b4b1 100644
--- a/src/solidlsp/language_servers/groovy_language_server.py
+++ b/src/solidlsp/language_servers/groovy_language_server.py
@@ -78,9 +78,9 @@ class GroovyLanguageServer(SolidLanguageServer):
platform_id = PlatformUtils.get_platform_id()
# Verify platform support
- assert (
- platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-")
- ), "Only Windows, Linux and macOS platforms are supported for Groovy in multilspy at the moment"
+ assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
+ "Only Windows, Linux and macOS platforms are supported for Groovy in multilspy at the moment"
+ )
# Check if user specified custom Java home path
java_home_path = None
diff --git a/src/solidlsp/language_servers/hlsl_language_server.py b/src/solidlsp/language_servers/hlsl_language_server.py
new file mode 100644
index 00000000..5ac09c26
--- /dev/null
+++ b/src/solidlsp/language_servers/hlsl_language_server.py
@@ -0,0 +1,252 @@
+"""
+Shader language server using shader-language-server (antaalt/shader-sense).
+Supports HLSL, GLSL, and WGSL shader file formats.
+"""
+
+import logging
+import os
+import pathlib
+import shutil
+from typing import Any, cast
+
+import psutil
+from overrides import override
+
+from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
+from solidlsp.ls_config import LanguageServerConfig
+from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
+from solidlsp.settings import SolidLSPSettings
+
+from .common import RuntimeDependency, RuntimeDependencyCollection
+
+log = logging.getLogger(__name__)
+
+# GitHub release version to download when not installed locally
+_DEFAULT_VERSION = "1.3.0"
+_GITHUB_RELEASE_BASE = "https://github.com/antaalt/shader-sense/releases/download"
+
+
+class HlslLanguageServer(SolidLanguageServer):
+ """
+ Shader language server using shader-language-server.
+ Supports .hlsl, .hlsli, .fx, .fxh, .cginc, .compute, .shader, .glsl, .vert, .frag, .geom, .tesc, .tese, .comp, .wgsl files.
+ """
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
+ super().__init__(config, repository_root_path, None, "hlsl", solidlsp_settings)
+
+ @override
+ def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
+ return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
+
+ class DependencyProvider(LanguageServerDependencyProviderSinglePath):
+ def _get_or_install_core_dependency(self) -> str:
+ # 1. Check PATH for system-installed binary
+ system_binary = shutil.which("shader-language-server")
+ if system_binary:
+ log.info(f"Using system-installed shader-language-server at {system_binary}")
+ return system_binary
+
+ # 2. Try to download pre-built binary from GitHub releases
+ version = self._custom_settings.get("version", _DEFAULT_VERSION)
+ tag = f"v{version}"
+ base_url = f"{_GITHUB_RELEASE_BASE}/{tag}"
+
+ # macOS has no pre-built binaries; build from source via cargo install
+ cargo_install_cmd = f"cargo install shader_language_server --version {version} --root ."
+
+ deps = RuntimeDependencyCollection(
+ [
+ RuntimeDependency(
+ id="shader-language-server",
+ description="shader-language-server for Windows (x64)",
+ url=f"{base_url}/shader-language-server-x86_64-pc-windows-msvc.zip",
+ platform_id="win-x64",
+ archive_type="zip",
+ binary_name="shader-language-server.exe",
+ ),
+ RuntimeDependency(
+ id="shader-language-server",
+ description="shader-language-server for Linux (x64)",
+ url=f"{base_url}/shader-language-server-x86_64-unknown-linux-gnu.zip",
+ platform_id="linux-x64",
+ archive_type="zip",
+ binary_name="shader-language-server",
+ ),
+ RuntimeDependency(
+ id="shader-language-server",
+ description="shader-language-server for Windows (ARM64)",
+ url=f"{base_url}/shader-language-server-aarch64-pc-windows-msvc.zip",
+ platform_id="win-arm64",
+ archive_type="zip",
+ binary_name="shader-language-server.exe",
+ ),
+ RuntimeDependency(
+ id="shader-language-server",
+ description="shader-language-server for macOS (x64) - built from source",
+ command=cargo_install_cmd,
+ platform_id="osx-x64",
+ binary_name="bin/shader-language-server",
+ ),
+ RuntimeDependency(
+ id="shader-language-server",
+ description="shader-language-server for macOS (ARM64) - built from source",
+ command=cargo_install_cmd,
+ platform_id="osx-arm64",
+ binary_name="bin/shader-language-server",
+ ),
+ ]
+ )
+
+ try:
+ dep = deps.get_single_dep_for_current_platform()
+ except RuntimeError:
+ dep = None
+
+ if dep is None:
+ raise FileNotFoundError(
+ "shader-language-server is not installed and no auto-install is available for your platform.\n"
+ "Please install it using one of the following methods:\n"
+ " cargo: cargo install shader_language_server\n"
+ " GitHub: Download from https://github.com/antaalt/shader-sense/releases\n"
+ "On macOS, install the Rust toolchain (https://rustup.rs) and Serena will build from source automatically.\n"
+ "See https://github.com/antaalt/shader-sense for more details."
+ )
+
+ install_dir = os.path.join(self._ls_resources_dir, "shader-language-server")
+ executable_path = deps.binary_path(install_dir)
+
+ if not os.path.exists(executable_path):
+ log.info(f"shader-language-server not found. Downloading from {dep.url}")
+ _ = deps.install(install_dir)
+
+ if not os.path.exists(executable_path):
+ raise FileNotFoundError(f"shader-language-server not found at {executable_path}")
+
+ os.chmod(executable_path, 0o755)
+ return executable_path
+
+ def _create_launch_command(self, core_path: str) -> list[str]:
+ return [core_path, "--stdio"]
+
+ @staticmethod
+ def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
+ root_uri = pathlib.Path(repository_absolute_path).as_uri()
+ initialize_params = {
+ "processId": os.getpid(),
+ "rootPath": repository_absolute_path,
+ "rootUri": root_uri,
+ "locale": "en",
+ "capabilities": {
+ "textDocument": {
+ "synchronization": {"didSave": True, "dynamicRegistration": True},
+ "completion": {
+ "dynamicRegistration": True,
+ "completionItem": {"snippetSupport": True},
+ },
+ "definition": {"dynamicRegistration": True},
+ "hover": {
+ "dynamicRegistration": True,
+ "contentFormat": ["markdown", "plaintext"],
+ },
+ "signatureHelp": {
+ "dynamicRegistration": True,
+ "signatureInformation": {
+ "parameterInformation": {"labelOffsetSupport": True},
+ },
+ },
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ "symbolKind": {"valueSet": list(range(1, 27))},
+ },
+ "formatting": {"dynamicRegistration": True},
+ "publishDiagnostics": {"relatedInformation": True},
+ },
+ "workspace": {
+ "workspaceFolders": True,
+ "didChangeConfiguration": {"dynamicRegistration": True},
+ "configuration": True,
+ },
+ },
+ "workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
+ }
+ return cast(InitializeParams, initialize_params)
+
+ @override
+ def _start_server(self) -> None:
+ def do_nothing(params: Any) -> None:
+ return
+
+ def on_log_message(params: Any) -> None:
+ message = params.get("message", "") if isinstance(params, dict) else str(params)
+ log.info(f"shader-language-server: {message}")
+
+ def on_configuration_request(params: Any) -> list[dict]:
+ """Respond to workspace/configuration requests.
+
+ shader-language-server requests config with section 'shader-validator'.
+ Return empty config to use defaults.
+ """
+ items = params.get("items", []) if isinstance(params, dict) else []
+ return [{}] * len(items)
+
+ self.server.on_request("client/registerCapability", do_nothing)
+ self.server.on_request("workspace/configuration", on_configuration_request)
+ self.server.on_notification("$/progress", do_nothing)
+ self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
+ self.server.on_notification("window/logMessage", on_log_message)
+
+ log.info("Starting shader-language-server process")
+ self.server.start()
+ initialize_params = self._get_initialize_params(self.repository_root_path)
+
+ log.info("Sending initialize request")
+ init_response = self.server.send.initialize(initialize_params)
+
+ capabilities = init_response.get("capabilities", {})
+ log.info(f"Initialize response capabilities: {list(capabilities.keys())}")
+ assert "textDocumentSync" in capabilities, "shader-language-server must support textDocumentSync"
+ if "documentSymbolProvider" not in capabilities:
+ log.warning("shader-language-server does not advertise documentSymbolProvider")
+ if "definitionProvider" not in capabilities:
+ log.warning("shader-language-server does not advertise definitionProvider")
+
+ self.server.notify.initialized({})
+
+ @override
+ def stop(self, shutdown_timeout: float = 2.0) -> None:
+ """Kill the shader-language-server process tree before the standard shutdown.
+
+ The base _shutdown() calls process.terminate() directly on the subprocess,
+ which on Windows with shell=True only kills the cmd.exe wrapper, leaving
+ the actual shader-language-server binary running as an orphan. We use psutil
+ to terminate the full process tree first.
+ """
+ process = self.server.process if self.server else None
+ if process and process.pid and process.returncode is None:
+ try:
+ parent = psutil.Process(process.pid)
+ children = parent.children(recursive=True)
+ for child in children:
+ try:
+ child.terminate()
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ psutil.wait_procs(children, timeout=2)
+ for child in children:
+ try:
+ if child.is_running():
+ child.kill()
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ except Exception as e:
+ log.debug(f"Error cleaning up shader-language-server process tree: {e}")
+ super().stop(shutdown_timeout)
+
+ @override
+ def is_ignored_dirname(self, dirname: str) -> bool:
+ """Ignore Unity-specific directories that contain no user-authored shaders."""
+ return super().is_ignored_dirname(dirname) or dirname in {"Library", "Temp", "Logs", "obj", "Packages"}
diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py
index e51c9da8..e5953c9d 100644
--- a/src/solidlsp/language_servers/intelephense.py
+++ b/src/solidlsp/language_servers/intelephense.py
@@ -76,9 +76,9 @@ class Intelephense(SolidLanguageServer):
)
deps.install(intelephense_ls_dir)
- assert os.path.exists(
- intelephense_executable_path
- ), f"intelephense executable not found at {intelephense_executable_path}, something went wrong."
+ assert os.path.exists(intelephense_executable_path), (
+ f"intelephense executable not found at {intelephense_executable_path}, something went wrong."
+ )
return intelephense_executable_path
@@ -112,8 +112,19 @@ class Intelephense(SolidLanguageServer):
"textDocument": {
"synchronization": {"didSave": True, "dynamicRegistration": True},
"definition": {"dynamicRegistration": True},
+ "references": {"dynamicRegistration": True},
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ "symbolKind": {"valueSet": list(range(1, 27))},
+ },
+ "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
+ },
+ "workspace": {
+ "workspaceFolders": True,
+ "didChangeConfiguration": {"dynamicRegistration": True},
+ "symbol": {"dynamicRegistration": True},
},
- "workspace": {"workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}},
},
"processId": os.getpid(),
"rootPath": repository_absolute_path,
@@ -167,9 +178,11 @@ class Intelephense(SolidLanguageServer):
log.info("After sent initialize params")
# Verify server capabilities
- assert "textDocumentSync" in init_response["capabilities"]
- assert "completionProvider" in init_response["capabilities"]
- assert "definitionProvider" in init_response["capabilities"]
+ capabilities = init_response["capabilities"]
+ assert "textDocumentSync" in capabilities
+ assert "completionProvider" in capabilities
+ assert "definitionProvider" in capabilities
+ assert "documentSymbolProvider" in capabilities, "Server must support document symbols"
self.server.notify.initialized({})
diff --git a/src/solidlsp/language_servers/kotlin_language_server.py b/src/solidlsp/language_servers/kotlin_language_server.py
index ac13ab2b..e8a04fc5 100644
--- a/src/solidlsp/language_servers/kotlin_language_server.py
+++ b/src/solidlsp/language_servers/kotlin_language_server.py
@@ -85,7 +85,6 @@ class KotlinLanguageServer(SolidLanguageServer):
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
def __init__(self, custom_settings: SolidLSPSettings.CustomLSSettings, ls_resources_dir: str):
super().__init__(custom_settings, ls_resources_dir)
- self._java_home_path: str | None = None
def _get_or_install_core_dependency(self) -> str:
"""
@@ -94,9 +93,9 @@ class KotlinLanguageServer(SolidLanguageServer):
platform_id = PlatformUtils.get_platform_id()
# Verify platform support
- assert (
- platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-")
- ), "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
+ assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
+ "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
+ )
kotlin_suffix = PLATFORM_KOTLIN_SUFFIX.get(platform_id.value)
assert kotlin_suffix, f"Unsupported platform for Kotlin LSP: {platform_id.value}"
@@ -134,9 +133,6 @@ class KotlinLanguageServer(SolidLanguageServer):
"""Provides JAVA_HOME and JVM options for the Kotlin Language Server process."""
env: dict[str, str] = {}
- if self._java_home_path is not None:
- env["JAVA_HOME"] = self._java_home_path
-
# Get JVM options from settings or use default
# Note: an explicit empty string means "no JVM options", which is distinct from not setting the key
_sentinel = object()
diff --git a/src/solidlsp/language_servers/lean4_language_server.py b/src/solidlsp/language_servers/lean4_language_server.py
new file mode 100644
index 00000000..463e4992
--- /dev/null
+++ b/src/solidlsp/language_servers/lean4_language_server.py
@@ -0,0 +1,175 @@
+"""
+Provides Lean 4 specific instantiation of the LanguageServer class.
+Uses the built-in Lean 4 language server (lean --server).
+"""
+
+import logging
+import os
+import pathlib
+import shutil
+import subprocess
+from typing import cast
+
+from overrides import override
+
+from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
+from solidlsp.ls_config import LanguageServerConfig
+from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
+from solidlsp.settings import SolidLSPSettings
+
+log = logging.getLogger(__name__)
+
+
+class Lean4LanguageServer(SolidLanguageServer):
+ """
+ Provides Lean 4 specific instantiation of the LanguageServer class.
+ Uses the built-in Lean 4 language server invoked via ``lean --server``.
+ Requires ``lean`` to be installed and available on PATH (typically via elan).
+ """
+
+ class DependencyProvider(LanguageServerDependencyProviderSinglePath):
+ def __init__(self, custom_settings: SolidLSPSettings.CustomLSSettings, ls_resources_dir: str, repository_root_path: str):
+ super().__init__(custom_settings, ls_resources_dir)
+ self._repository_root_path = repository_root_path
+
+ def _get_or_install_core_dependency(self) -> str:
+ lean_path = shutil.which("lean")
+ if lean_path is None:
+ raise RuntimeError(
+ "lean is not installed or not in PATH.\n"
+ "Please install Lean 4 via elan: https://github.com/leanprover/elan\n"
+ " curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh\n"
+ "After installation, make sure 'lean' is available on your PATH."
+ )
+ return lean_path
+
+ def _create_launch_command(self, core_path: str) -> list[str]:
+ return [core_path, "--server"]
+
+ @override
+ def create_launch_command_env(self) -> dict[str, str]:
+ """Provides LEAN_PATH and LEAN_SRC_PATH from ``lake env`` for cross-file references."""
+ env: dict[str, str] = {}
+ lake_path = shutil.which("lake")
+ if lake_path is None:
+ log.warning("lake not found on PATH; cross-file references may not work")
+ return env
+ try:
+ result = subprocess.run(
+ [lake_path, "env"],
+ check=False,
+ cwd=self._repository_root_path,
+ capture_output=True,
+ text=True,
+ timeout=30,
+ )
+ if result.returncode == 0:
+ for line in result.stdout.splitlines():
+ if "=" in line:
+ key, _, value = line.partition("=")
+ if key in ("LEAN_PATH", "LEAN_SRC_PATH"):
+ env[key] = value
+ log.info(f"Lake env: {key}={value}")
+ else:
+ log.warning(f"lake env failed (exit {result.returncode}): {result.stderr[:200]}")
+ except Exception as e:
+ log.warning(f"Failed to run lake env: {e}")
+ return env
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
+ """
+ Creates a Lean4LanguageServer instance. This class is not meant to be
+ instantiated directly. Use LanguageServer.create() instead.
+ """
+ super().__init__(
+ config,
+ repository_root_path,
+ None,
+ "lean4",
+ solidlsp_settings,
+ )
+
+ def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
+ return self.DependencyProvider(self._custom_settings, self._ls_resources_dir, self.repository_root_path)
+
+ @override
+ def is_ignored_dirname(self, dirname: str) -> bool:
+ return super().is_ignored_dirname(dirname) or dirname in [".lake", "build"]
+
+ @staticmethod
+ def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
+ """
+ Returns the initialize params for the Lean 4 Language Server.
+ """
+ root_uri = pathlib.Path(repository_absolute_path).as_uri()
+ initialize_params = {
+ "processId": os.getpid(),
+ "rootPath": repository_absolute_path,
+ "rootUri": root_uri,
+ "capabilities": {
+ "textDocument": {
+ "synchronization": {"didSave": True, "dynamicRegistration": True},
+ "definition": {"dynamicRegistration": True, "linkSupport": True},
+ "references": {"dynamicRegistration": True},
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ "symbolKind": {"valueSet": list(range(1, 27))},
+ },
+ "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
+ "completion": {
+ "dynamicRegistration": True,
+ "completionItem": {
+ "snippetSupport": True,
+ "documentationFormat": ["markdown", "plaintext"],
+ },
+ },
+ },
+ "workspace": {
+ "workspaceFolders": True,
+ "didChangeConfiguration": {"dynamicRegistration": True},
+ },
+ },
+ "workspaceFolders": [
+ {
+ "uri": root_uri,
+ "name": os.path.basename(repository_absolute_path),
+ }
+ ],
+ }
+ return cast(InitializeParams, initialize_params)
+
+ def _start_server(self) -> None:
+ """Start the Lean 4 language server process."""
+
+ def register_capability_handler(_params: dict) -> None:
+ return
+
+ def window_log_message(msg: dict) -> None:
+ log.info(f"LSP: window/logMessage: {msg}")
+
+ def do_nothing(_params: dict) -> None:
+ return
+
+ 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.server.on_notification("$/lean/fileProgress", do_nothing)
+
+ log.info("Starting Lean 4 language server process")
+ self.server.start()
+ initialize_params = self._get_initialize_params(self.repository_root_path)
+
+ log.info("Sending initialize request from LSP client to LSP server and awaiting response")
+ init_response = self.server.send.initialize(initialize_params)
+
+ capabilities = init_response.get("capabilities", {})
+ log.info(f"Lean 4 LSP capabilities: {list(capabilities.keys())}")
+
+ self.server.notify.initialized({})
+
+ @override
+ def _get_wait_time_for_cross_file_referencing(self) -> float:
+ """Lean 4 projects need time to compile and build cross-file references."""
+ return 10.0
diff --git a/src/solidlsp/language_servers/luau_lsp.py b/src/solidlsp/language_servers/luau_lsp.py
new file mode 100644
index 00000000..9aaf254a
--- /dev/null
+++ b/src/solidlsp/language_servers/luau_lsp.py
@@ -0,0 +1,363 @@
+"""
+Provides Luau specific instantiation of the LanguageServer class using luau-lsp.
+
+Luau is the programming language used by Roblox, derived from Lua 5.1 with
+additional features like type annotations, string interpolation, and more.
+This uses JohnnyMorganz/luau-lsp as the language server backend.
+
+Requirements:
+ - luau-lsp binary must be installed and available in PATH,
+ or it will be automatically downloaded from GitHub releases.
+
+Advanced settings via ls_specific_settings["luau"]:
+ - platform: "roblox" (default) or "standard"
+ - roblox_security_level: "None", "PluginSecurity" (default),
+ "LocalUserSecurity", or "RobloxScriptSecurity"
+
+See: https://github.com/JohnnyMorganz/luau-lsp
+"""
+
+import logging
+import os
+import pathlib
+import platform
+import shutil
+import threading
+import zipfile
+from pathlib import Path
+
+import requests
+from overrides import override
+
+from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
+from solidlsp.ls_config import LanguageServerConfig
+from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
+from solidlsp.settings import SolidLSPSettings
+
+log = logging.getLogger(__name__)
+
+# Pin to a known stable release
+LUAU_LSP_VERSION = "1.63.0"
+
+# Luau built-in docs CDN
+LUAU_DOCS_URL = "https://luau-lsp.pages.dev/api-docs/luau-en-us.json"
+
+# Roblox type definitions and API docs CDN
+ROBLOX_DOCS_URL = "https://luau-lsp.pages.dev/api-docs/en-us.json"
+SUPPORTED_PLATFORMS = {"roblox", "standard"}
+SUPPORTED_ROBLOX_SECURITY_LEVELS = {
+ "None",
+ "PluginSecurity",
+ "LocalUserSecurity",
+ "RobloxScriptSecurity",
+}
+
+
+class LuauLanguageServer(SolidLanguageServer):
+ """
+ Provides Luau specific instantiation of the LanguageServer class using luau-lsp.
+ Luau is the programming language used by Roblox (a typed superset of Lua 5.1).
+ """
+
+ @override
+ def is_ignored_dirname(self, dirname: str) -> bool:
+ return super().is_ignored_dirname(dirname) or dirname in [
+ "node_modules",
+ "Packages", # Wally packages
+ "DevPackages", # Wally dev packages
+ "roblox_packages", # Some Rojo projects
+ "build",
+ "dist",
+ ".cache",
+ ]
+
+ class DependencyProvider(LanguageServerDependencyProviderSinglePath):
+ def _get_or_install_core_dependency(self) -> str:
+ luau_lsp_path = shutil.which("luau-lsp")
+ if luau_lsp_path is not None:
+ return luau_lsp_path
+ return self._download_luau_lsp()
+
+ def _create_launch_command(self, core_path: str) -> list[str]:
+ definitions_path, docs_path = self._resolve_support_files()
+
+ cmd = [core_path, "lsp"]
+ if definitions_path is not None:
+ cmd.append(f"--definitions:@roblox={definitions_path}")
+ if docs_path is not None:
+ cmd.append(f"--docs={docs_path}")
+ return cmd
+
+ def _download_luau_lsp(self) -> str:
+ install_dir = Path(self._ls_resources_dir)
+ install_dir.mkdir(parents=True, exist_ok=True)
+
+ binary_path = self._find_existing_binary(install_dir)
+ if binary_path is not None:
+ return binary_path
+
+ asset_name = self._get_luau_lsp_asset_name()
+ download_url = f"https://github.com/JohnnyMorganz/luau-lsp/releases/download/{LUAU_LSP_VERSION}/{asset_name}"
+ download_path = install_dir / asset_name
+
+ log.info("Downloading luau-lsp %s from %s", LUAU_LSP_VERSION, download_url)
+ with requests.get(download_url, stream=True, timeout=60) as response:
+ response.raise_for_status()
+ with open(download_path, "wb") as f:
+ for chunk in response.iter_content(chunk_size=8192):
+ if chunk:
+ f.write(chunk)
+
+ log.info("Extracting luau-lsp to %s", install_dir)
+ with zipfile.ZipFile(download_path, "r") as zip_ref:
+ zip_ref.extractall(install_dir)
+
+ if download_path.exists():
+ download_path.unlink()
+
+ binary_path = self._find_existing_binary(install_dir)
+ if binary_path is None:
+ raise RuntimeError("Failed to find luau-lsp executable after extraction")
+
+ return binary_path
+
+ def _resolve_support_files(self) -> tuple[str | None, str | None]:
+ platform_type = LuauLanguageServer._get_platform_type(self._custom_settings)
+ if platform_type == "standard":
+ return None, self._download_standard_docs()
+
+ security_level = LuauLanguageServer._get_roblox_security_level(self._custom_settings)
+ return self._download_roblox_support_files(security_level)
+
+ def _download_standard_docs(self) -> str | None:
+ install_dir = Path(self._ls_resources_dir)
+ install_dir.mkdir(parents=True, exist_ok=True)
+
+ return self._download_auxiliary_file(
+ install_dir / "luau-en-us.json",
+ LUAU_DOCS_URL,
+ "Luau API docs",
+ )
+
+ def _download_roblox_support_files(self, security_level: str) -> tuple[str | None, str | None]:
+ install_dir = Path(self._ls_resources_dir)
+ install_dir.mkdir(parents=True, exist_ok=True)
+
+ definitions_filename = f"globalTypes.{security_level}.d.luau"
+ definitions_path = self._download_auxiliary_file(
+ install_dir / definitions_filename,
+ f"https://luau-lsp.pages.dev/type-definitions/{definitions_filename}",
+ "Roblox type definitions",
+ )
+ docs_path = self._download_auxiliary_file(
+ install_dir / "en-us.json",
+ ROBLOX_DOCS_URL,
+ "Roblox API docs",
+ )
+
+ return definitions_path, docs_path
+
+ @staticmethod
+ def _download_auxiliary_file(path: Path, url: str, description: str) -> str | None:
+ if path.exists():
+ return str(path)
+
+ try:
+ log.info("Downloading %s from %s", description, url)
+ response = requests.get(url, timeout=30)
+ response.raise_for_status()
+ path.write_bytes(response.content)
+ return str(path)
+ except Exception as exc:
+ log.warning("Failed to download %s: %s", description, exc)
+ return None
+
+ @classmethod
+ def _find_existing_binary(cls, install_dir: Path) -> str | None:
+ binary_name = cls._get_binary_name()
+ direct_path = install_dir / binary_name
+ if direct_path.exists():
+ cls._ensure_executable_bit(direct_path)
+ return str(direct_path)
+
+ for candidate in install_dir.rglob(binary_name):
+ if candidate.is_file():
+ cls._ensure_executable_bit(candidate)
+ return str(candidate)
+
+ return None
+
+ @staticmethod
+ def _ensure_executable_bit(binary_path: Path) -> None:
+ if platform.system() != "Windows":
+ binary_path.chmod(0o755)
+
+ @staticmethod
+ def _get_binary_name() -> str:
+ return "luau-lsp.exe" if platform.system() == "Windows" else "luau-lsp"
+
+ @staticmethod
+ def _get_luau_lsp_asset_name() -> str:
+ system = platform.system()
+ machine = platform.machine().lower()
+
+ if system == "Linux":
+ if machine in ["x86_64", "amd64"]:
+ return "luau-lsp-linux-x86_64.zip"
+ if machine in ["aarch64", "arm64"]:
+ return "luau-lsp-linux-arm64.zip"
+ raise RuntimeError(
+ f"Unsupported Linux architecture: {machine}. "
+ "luau-lsp only provides linux-x86_64 and linux-arm64 binaries. "
+ "Please build from source: https://github.com/JohnnyMorganz/luau-lsp"
+ )
+ if system == "Darwin":
+ return "luau-lsp-macos.zip"
+ if system == "Windows":
+ return "luau-lsp-win64.zip"
+ raise RuntimeError(f"Unsupported operating system: {system}")
+
+ @staticmethod
+ def _get_platform_type(custom_settings: SolidLSPSettings.CustomLSSettings) -> str:
+ platform_type = custom_settings.get("platform", "roblox")
+ if platform_type not in SUPPORTED_PLATFORMS:
+ raise ValueError(f"Unsupported Luau platform: {platform_type}. Expected one of: {', '.join(sorted(SUPPORTED_PLATFORMS))}")
+ return platform_type
+
+ @staticmethod
+ def _get_roblox_security_level(custom_settings: SolidLSPSettings.CustomLSSettings) -> str:
+ security_level = custom_settings.get("roblox_security_level", "PluginSecurity")
+ if security_level not in SUPPORTED_ROBLOX_SECURITY_LEVELS:
+ raise ValueError(
+ f"Unsupported Luau Roblox security level: {security_level}. "
+ f"Expected one of: {', '.join(sorted(SUPPORTED_ROBLOX_SECURITY_LEVELS))}"
+ )
+ return security_level
+
+ @classmethod
+ def _get_workspace_configuration(cls, custom_settings: SolidLSPSettings.CustomLSSettings) -> dict[str, dict[str, str]]:
+ return {"platform": {"type": cls._get_platform_type(custom_settings)}}
+
+ def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
+ return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
+ super().__init__(config, repository_root_path, None, "luau", solidlsp_settings)
+ self.server_ready = threading.Event()
+
+ @staticmethod
+ def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
+ """
+ Returns the initialize params for the Luau Language Server.
+ """
+ root_uri = pathlib.Path(repository_absolute_path).as_uri()
+ initialize_params = {
+ "locale": "en",
+ "capabilities": {
+ "textDocument": {
+ "synchronization": {"didSave": True, "dynamicRegistration": True},
+ "definition": {"dynamicRegistration": True},
+ "references": {"dynamicRegistration": True},
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ "symbolKind": {"valueSet": list(range(1, 27))},
+ },
+ "completion": {
+ "dynamicRegistration": True,
+ "completionItem": {
+ "snippetSupport": True,
+ "commitCharactersSupport": True,
+ "documentationFormat": ["markdown", "plaintext"],
+ "deprecatedSupport": True,
+ "preselectSupport": True,
+ },
+ },
+ "hover": {
+ "dynamicRegistration": True,
+ "contentFormat": ["markdown", "plaintext"],
+ },
+ "signatureHelp": {
+ "dynamicRegistration": True,
+ "signatureInformation": {
+ "documentationFormat": ["markdown", "plaintext"],
+ "parameterInformation": {"labelOffsetSupport": True},
+ },
+ },
+ "rename": {"dynamicRegistration": True, "prepareSupport": True},
+ "callHierarchy": {"dynamicRegistration": True},
+ },
+ "workspace": {
+ "workspaceFolders": True,
+ "didChangeConfiguration": {"dynamicRegistration": True},
+ "configuration": 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),
+ }
+ ],
+ # luau-lsp initialization options
+ # These can be overridden via .luaurc in the project root
+ "initializationOptions": {},
+ }
+ return initialize_params # type: ignore[return-value]
+
+ def _start_server(self) -> None:
+ """Start Luau Language Server process"""
+
+ def register_capability_handler(params: dict) -> None:
+ return
+
+ def workspace_configuration_handler(params: dict) -> list:
+ items = params.get("items", [])
+ config = self._get_workspace_configuration(self._custom_settings)
+ return [config for _ in items]
+
+ def window_log_message(msg: dict) -> None:
+ message_text = msg.get("message", "")
+ log.info("LSP: window/logMessage: %s", message_text)
+ if "workspace ready" in message_text.lower() or "initialized" in message_text.lower():
+ log.info("Luau language server signaled readiness")
+ self.server_ready.set()
+
+ def do_nothing(params: dict) -> None:
+ return
+
+ self.server.on_request("client/registerCapability", register_capability_handler)
+ self.server.on_request("workspace/configuration", workspace_configuration_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)
+
+ log.info("Starting Luau Language Server process")
+ self.server.start()
+ initialize_params = self._get_initialize_params(self.repository_root_path)
+
+ log.info("Sending initialize request from LSP client to LSP server and awaiting response")
+ init_response = self.server.send.initialize(initialize_params)
+
+ # Verify server capabilities
+ assert "textDocumentSync" in init_response["capabilities"]
+ assert "definitionProvider" in init_response["capabilities"]
+ assert "documentSymbolProvider" in init_response["capabilities"]
+ assert "referencesProvider" in init_response["capabilities"]
+
+ self.server.notify.initialized({})
+
+ # Wait for luau-lsp to complete initial setup
+ log.info("Waiting for Luau language server to become ready...")
+ if self.server_ready.wait(timeout=5.0):
+ log.info("Luau language server ready")
+ else:
+ log.warning("Timeout waiting for Luau language server readiness, proceeding anyway")
+ self.server_ready.set()
diff --git a/src/solidlsp/language_servers/ocaml_lsp_server.py b/src/solidlsp/language_servers/ocaml_lsp_server.py
new file mode 100644
index 00000000..8c9bc356
--- /dev/null
+++ b/src/solidlsp/language_servers/ocaml_lsp_server.py
@@ -0,0 +1,430 @@
+"""
+Provides OCaml and Reason specific instantiation of the SolidLanguageServer class.
+Contains various configurations and settings specific to OCaml and Reason.
+"""
+
+import logging
+import os
+import pathlib
+import platform
+import re
+import shutil
+import stat
+import subprocess
+import threading
+from typing import Any
+
+from overrides import override
+
+from solidlsp.ls import SolidLanguageServer
+from solidlsp.ls_config import LanguageServerConfig
+from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
+from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
+from solidlsp.settings import SolidLSPSettings
+from solidlsp.util.subprocess_util import subprocess_kwargs
+
+log = logging.getLogger(__name__)
+
+
+class OcamlLanguageServer(SolidLanguageServer):
+ """
+ Provides OCaml and Reason specific instantiation of the SolidLanguageServer class.
+ Contains various configurations and settings specific to OCaml and Reason.
+ """
+
+ _ocaml_version: tuple[int, int, int]
+ _lsp_version: tuple[int, int, int]
+ _index_built: bool
+
+ # Minimum LSP version for reliable cross-file references
+ MIN_LSP_VERSION_FOR_CROSS_FILE_REFS: tuple[int, int, int] = (1, 23, 0)
+
+ @staticmethod
+ def _ensure_opam_installed() -> None:
+ """Ensure OPAM is installed and available."""
+ opam_path = shutil.which("opam")
+ if opam_path is None:
+ raise RuntimeError(
+ "OPAM is not installed or not in PATH.\n"
+ "Please install OPAM from: https://opam.ocaml.org/doc/Install.html\n\n"
+ "Installation instructions:\n"
+ " - macOS: brew install opam\n"
+ " - Ubuntu/Debian: sudo apt install opam\n"
+ " - Fedora: sudo dnf install opam\n"
+ " - Windows: https://fdopen.github.io/opam-repository-mingw/installation/\n\n"
+ "After installation, initialize OPAM with: opam init"
+ )
+
+ @staticmethod
+ def _detect_ocaml_version(repository_root_path: str) -> tuple[int, int, int]:
+ """
+ Detect and return the OCaml version as a tuple (major, minor, patch).
+ Also checks for version compatibility with ocaml-lsp-server.
+ Raises RuntimeError if version cannot be determined.
+ """
+ try:
+ result = subprocess.run(
+ ["opam", "exec", "--", "ocaml", "-version"],
+ check=True,
+ capture_output=True,
+ text=True,
+ cwd=repository_root_path,
+ **subprocess_kwargs(),
+ )
+ version_match = re.search(r"(\d+)\.(\d+)\.(\d+)", result.stdout)
+ if version_match:
+ major = int(version_match.group(1))
+ minor = int(version_match.group(2))
+ patch = int(version_match.group(3))
+ version_tuple = (major, minor, patch)
+ version_str = f"{major}.{minor}.{patch}"
+ log.info(f"OCaml version: {version_str}")
+
+ if version_tuple == (5, 1, 0):
+ raise RuntimeError(
+ f"OCaml {version_str} is incompatible with ocaml-lsp-server.\n"
+ "Please use OCaml < 5.1 or >= 5.1.1.\n"
+ "Consider creating a new opam switch:\n"
+ " opam switch create
ocaml-base-compiler.4.14.2"
+ )
+ return version_tuple
+ raise RuntimeError(
+ f"Could not parse OCaml version from output: {result.stdout.strip()}\n"
+ "Please ensure OCaml is properly installed: opam exec -- ocaml -version"
+ )
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError(
+ f"Failed to detect OCaml version: {e.stderr}\n"
+ "Please ensure OCaml is installed and opam is configured:\n"
+ " opam switch show\n"
+ " opam exec -- ocaml -version"
+ ) from e
+ except FileNotFoundError as e:
+ raise RuntimeError(
+ "OCaml not found. Please install OCaml via opam:\n"
+ " opam switch create ocaml-base-compiler.4.14.2\n"
+ " eval $(opam env)"
+ ) from e
+
+ @staticmethod
+ def _detect_lsp_version(repository_root_path: str) -> tuple[int, int, int]:
+ """
+ Detect and return the ocaml-lsp-server version as a tuple (major, minor, patch).
+ Raises RuntimeError if version cannot be determined.
+ """
+ try:
+ result = subprocess.run(
+ ["opam", "list", "-i", "ocaml-lsp-server", "--columns=version", "--short"],
+ check=True,
+ capture_output=True,
+ text=True,
+ cwd=repository_root_path,
+ **subprocess_kwargs(),
+ )
+ version_str = result.stdout.strip()
+ version_match = re.search(r"(\d+)\.(\d+)\.(\d+)", version_str)
+ if version_match:
+ major = int(version_match.group(1))
+ minor = int(version_match.group(2))
+ patch = int(version_match.group(3))
+ version_tuple = (major, minor, patch)
+ log.info(f"ocaml-lsp-server version: {major}.{minor}.{patch}")
+ return version_tuple
+ raise RuntimeError(
+ f"Could not parse ocaml-lsp-server version from output: {version_str}\n"
+ "Please ensure ocaml-lsp-server is properly installed:\n"
+ " opam list -i ocaml-lsp-server"
+ )
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError(
+ f"Failed to detect ocaml-lsp-server version: {e.stderr}\nPlease install ocaml-lsp-server:\n opam install ocaml-lsp-server"
+ ) from e
+ except FileNotFoundError as e:
+ raise RuntimeError("opam not found. Please install opam:\n https://opam.ocaml.org/doc/Install.html") from e
+
+ @staticmethod
+ def _ensure_ocaml_lsp_installed(repository_root_path: str) -> str:
+ """
+ Ensure ocaml-lsp-server is installed and return the executable path.
+ Raises RuntimeError with helpful message if not installed.
+ """
+ # Check if ocaml-lsp-server is installed
+ try:
+ result = subprocess.run(
+ ["opam", "list", "-i", "ocaml-lsp-server"],
+ check=False,
+ capture_output=True,
+ text=True,
+ cwd=repository_root_path,
+ **subprocess_kwargs(),
+ )
+ if "ocaml-lsp-server" not in result.stdout or "# No matches found" in result.stdout:
+ raise RuntimeError(
+ "ocaml-lsp-server is not installed.\n\n"
+ "Please install it with:\n"
+ " opam install ocaml-lsp-server\n\n"
+ "Note: ocaml-lsp-server requires OCaml < 5.1 or >= 5.1.1 (OCaml 5.1.0 is not supported).\n"
+ "If you have OCaml 5.1.0, create a new opam switch with a compatible version:\n"
+ " opam switch create ocaml-base-compiler.4.14.2\n"
+ " opam switch \n"
+ " eval $(opam env)\n"
+ " opam install ocaml-lsp-server\n\n"
+ "For more information: https://github.com/ocaml/ocaml-lsp"
+ )
+ log.info("ocaml-lsp-server is installed")
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError(f"Failed to check ocaml-lsp-server installation: {e.stderr}")
+
+ # Find the executable path
+ try:
+ if platform.system() == "Windows":
+ result = subprocess.run(
+ ["opam", "exec", "--", "where", "ocamllsp"],
+ check=True,
+ capture_output=True,
+ text=True,
+ cwd=repository_root_path,
+ **subprocess_kwargs(),
+ )
+ executable_path = result.stdout.strip().split("\n")[0]
+ else:
+ result = subprocess.run(
+ ["opam", "exec", "--", "which", "ocamllsp"],
+ check=True,
+ capture_output=True,
+ text=True,
+ cwd=repository_root_path,
+ **subprocess_kwargs(),
+ )
+ executable_path = result.stdout.strip()
+
+ if not os.path.exists(executable_path):
+ raise RuntimeError(f"ocaml-lsp-server executable not found at {executable_path}")
+
+ if platform.system() != "Windows":
+ os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IEXEC)
+
+ return executable_path
+
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError(
+ f"Failed to find ocaml-lsp-server executable.\n"
+ f"Command failed: {e.cmd}\n"
+ f"Return code: {e.returncode}\n"
+ f"Stderr: {e.stderr}\n\n"
+ "This usually means ocaml-lsp-server is not installed or not in PATH.\n"
+ "Try:\n"
+ " 1. Check opam switch: opam switch show\n"
+ " 2. Install ocaml-lsp-server: opam install ocaml-lsp-server\n"
+ " 3. Ensure opam env is activated: eval $(opam env)"
+ )
+
+ @property
+ def supports_cross_file_references(self) -> bool:
+ """
+ Check if this OCaml environment supports cross-file references.
+
+ Cross-file references require OCaml >= 5.2 with project-wide occurrences
+ AND ocaml-lsp-server >= 1.23.0 for reliable cross-file reference support.
+ Full requirements:
+ - OCaml 5.2+
+ - ocaml-lsp-server >= 1.23.0 (earlier versions have unreliable cross-file refs)
+ - merlin >= 5.1-502 (provides ocaml-index tool)
+ - dune >= 3.16.0
+ - Index built via `dune build @ocaml-index`
+ - For best results: `dune build -w` running (enables dune RPC)
+
+ Note: Even when this returns True, cross-file refs may not work in all
+ cases. The LSP server needs dune's RPC server (via -w flag) to be fully
+ aware of the index. Without watch mode, cross-file refs are best-effort.
+
+ See: https://discuss.ocaml.org/t/ann-project-wide-occurrences-in-merlin-and-lsp/14847
+ """
+ ocaml_ok = self._ocaml_version >= (5, 2, 0)
+ lsp_ok = self._lsp_version >= self.MIN_LSP_VERSION_FOR_CROSS_FILE_REFS
+ return ocaml_ok and lsp_ok
+
+ @staticmethod
+ def _build_ocaml_index_static(repository_root_path: str) -> bool:
+ """
+ Build the OCaml index for project-wide occurrences.
+ This enables cross-file reference finding on OCaml 5.2+.
+ Must be called BEFORE starting the LSP server.
+ Returns True if successful, False otherwise.
+ """
+ log.info("Building OCaml index for cross-file references (dune build @ocaml-index)...")
+ try:
+ result = subprocess.run(
+ ["opam", "exec", "--", "dune", "build", "@ocaml-index"],
+ cwd=repository_root_path,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ check=False,
+ **subprocess_kwargs(),
+ )
+ if result.returncode == 0:
+ log.info("OCaml index built successfully")
+ return True
+ else:
+ log.warning(f"Failed to build OCaml index: {result.stderr}")
+ return False
+ except subprocess.TimeoutExpired:
+ log.warning("OCaml index build timed out after 120 seconds")
+ return False
+ except FileNotFoundError:
+ log.warning("opam not found, cannot build OCaml index")
+ return False
+ except Exception as e:
+ log.warning(f"Error building OCaml index: {e}")
+ return False
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
+ """
+ Creates an OcamlLanguageServer instance.
+ This class is not meant to be instantiated directly. Use SolidLanguageServer.create() instead.
+ """
+ # Ensure dependencies are available
+ self._ensure_opam_installed()
+
+ # Detect OCaml version for feature gating
+ self._ocaml_version = self._detect_ocaml_version(repository_root_path)
+ self._index_built = False
+
+ # Verify ocaml-lsp-server is installed (we don't need the path, just validation)
+ self._ensure_ocaml_lsp_installed(repository_root_path)
+
+ # Detect LSP version for cross-file reference support
+ self._lsp_version = self._detect_lsp_version(repository_root_path)
+
+ # Build OCaml index BEFORE starting server (required for cross-file refs on OCaml 5.2+)
+ if self._ocaml_version >= (5, 2, 0):
+ self._index_built = self._build_ocaml_index_static(repository_root_path)
+
+ # Use opam exec to run ocamllsp - this ensures correct opam environment
+ # which is required for project-wide occurrences (cross-file references) to work
+ ocaml_lsp_cmd = ["opam", "exec", "--", "ocamllsp", "--fallback-read-dot-merlin"]
+ log.info(f"Using ocaml-lsp-server via: {' '.join(ocaml_lsp_cmd)}")
+
+ super().__init__(
+ config,
+ repository_root_path,
+ ProcessLaunchInfo(cmd=ocaml_lsp_cmd, cwd=repository_root_path),
+ "ocaml",
+ solidlsp_settings,
+ )
+ self.server_ready = threading.Event()
+ self.completions_available = threading.Event()
+
+ @override
+ def is_ignored_dirname(self, dirname: str) -> bool:
+ """Define language-specific directories to ignore for OCaml projects."""
+ return super().is_ignored_dirname(dirname) or dirname in ["_build", "_opam", ".opam"]
+
+ @staticmethod
+ def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
+ """
+ Returns the initialize params for the OCaml Language Server.
+ Supports both OCaml and Reason.
+ """
+ root_uri = pathlib.Path(repository_absolute_path).as_uri()
+ initialize_params = {
+ "processId": os.getpid(),
+ "clientInfo": {"name": "Serena", "version": "0.1.0"},
+ "locale": "en",
+ "rootPath": repository_absolute_path,
+ "rootUri": root_uri,
+ "capabilities": {
+ "workspace": {
+ "workspaceFolders": True,
+ "configuration": True,
+ },
+ "textDocument": {
+ "synchronization": {
+ "dynamicRegistration": True,
+ "willSave": True,
+ "willSaveWaitUntil": True,
+ "didSave": True,
+ },
+ "completion": {
+ "dynamicRegistration": True,
+ "completionItem": {
+ "snippetSupport": True,
+ "documentationFormat": ["markdown", "plaintext"],
+ },
+ },
+ "hover": {
+ "dynamicRegistration": True,
+ "contentFormat": ["markdown", "plaintext"],
+ },
+ "definition": {"dynamicRegistration": True, "linkSupport": True},
+ "references": {"dynamicRegistration": True},
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ },
+ "formatting": {"dynamicRegistration": True},
+ "rename": {"dynamicRegistration": True, "prepareSupport": True},
+ },
+ },
+ "trace": "verbose",
+ "workspaceFolders": [
+ {
+ "uri": root_uri,
+ "name": os.path.basename(repository_absolute_path),
+ }
+ ],
+ }
+ return initialize_params # type: ignore[return-value]
+
+ def _start_server(self) -> None:
+ """
+ Starts the OCaml Language Server (supports both OCaml and Reason)
+ """
+
+ def register_capability_handler(params: Any) -> None:
+ if "registrations" in params:
+ for registration in params.get("registrations", []):
+ method = registration.get("method", "")
+ log.info(f"OCaml LSP registered capability: {method}")
+ return
+
+ def lang_status_handler(params: dict[str, Any]) -> None:
+ if params.get("type") == "ServiceReady" and params.get("message") == "ServiceReady":
+ self.server_ready.set()
+
+ def do_nothing(params: Any) -> None:
+ return
+
+ def window_log_message(msg: dict[str, Any]) -> None:
+ log.info(f"LSP: window/logMessage: {msg}")
+ if "initialization done" in msg.get("message", "").lower():
+ self.server_ready.set()
+
+ self.server.on_request("client/registerCapability", register_capability_handler)
+ self.server.on_notification("language/status", lang_status_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)
+
+ log.info("Starting OCaml LSP server process")
+ self.server.start()
+ initialize_params = self._get_initialize_params(self.repository_root_path)
+
+ log.info("Sending initialize request from LSP client to LSP server and awaiting response")
+ init_response = self.server.send.initialize(initialize_params)
+
+ # Verify expected capabilities
+ capabilities = init_response.get("capabilities", {})
+ log.info(f"OCaml LSP capabilities: {list(capabilities.keys())}")
+
+ text_doc_sync = capabilities.get("textDocumentSync")
+ if isinstance(text_doc_sync, dict):
+ assert text_doc_sync.get("change") == 2, "Expected incremental sync"
+ assert "completionProvider" in capabilities, "Expected completion support"
+
+ self.server.notify.initialized({})
+ self.completions_available.set()
+ self.server_ready.set()
+
+ log.info("OCaml Language Server initialized successfully")
diff --git a/src/solidlsp/language_servers/phpactor.py b/src/solidlsp/language_servers/phpactor.py
index 2d10ca1c..d10a038b 100644
--- a/src/solidlsp/language_servers/phpactor.py
+++ b/src/solidlsp/language_servers/phpactor.py
@@ -46,9 +46,9 @@ class PhpactorServer(SolidLanguageServer):
"""
# Verify PHP is installed
php_path = shutil.which("php")
- assert (
- php_path is not None
- ), "PHP is not installed or not found in PATH. Phpactor requires PHP 8.1+. Please install PHP and try again."
+ assert php_path is not None, (
+ "PHP is not installed or not found in PATH. Phpactor requires PHP 8.1+. Please install PHP and try again."
+ )
# Check PHP version (Phpactor requires PHP 8.1+)
result = subprocess.run(["php", "--version"], capture_output=True, text=True, check=False)
diff --git a/src/solidlsp/language_servers/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer.py
index b7c95947..f0ff00e6 100644
--- a/src/solidlsp/language_servers/rust_analyzer.py
+++ b/src/solidlsp/language_servers/rust_analyzer.py
@@ -65,6 +65,19 @@ class RustAnalyzer(SolidLanguageServer):
pass
return None
+ @staticmethod
+ def _is_rust_analyzer_functional(path: str) -> bool:
+ """Check if a rust-analyzer binary is functional by running --version.
+
+ This catches the case where rust-analyzer in PATH is actually a rustup proxy
+ that fails because the component is not installed.
+ """
+ try:
+ result = subprocess.run([path, "--version"], capture_output=True, text=True, check=False, timeout=10)
+ return result.returncode == 0
+ except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
+ return False
+
@staticmethod
def _ensure_rust_analyzer_installed() -> str:
"""
@@ -73,8 +86,8 @@ class RustAnalyzer(SolidLanguageServer):
Priority order:
1. Rustup existing installation (preferred - matches toolchain version)
2. Rustup auto-install if rustup is available (ensures correct version)
- 3. Common installation locations as fallback (only if rustup not available)
- 4. System PATH last (can pick up incompatible versions)
+ 3. System PATH (covers Nix, distro packages, and other standalone installs)
+ 4. Common installation locations as final fallback
:return: path to rust-analyzer executable
"""
@@ -84,7 +97,7 @@ class RustAnalyzer(SolidLanguageServer):
return rustup_path
# If rustup is available but rust-analyzer not installed, auto-install it BEFORE
- # checking common paths. This ensures we get the correct version matching the toolchain.
+ # checking PATH. This ensures we get the correct version matching the toolchain.
if RustAnalyzer.DependencyProvider._get_rustup_version():
result = subprocess.run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True)
if result.returncode == 0:
@@ -92,13 +105,20 @@ class RustAnalyzer(SolidLanguageServer):
rustup_path = RustAnalyzer.DependencyProvider._get_rust_analyzer_via_rustup()
if rustup_path:
return rustup_path
- # If auto-install failed, fall through to common paths as last resort
+ # If auto-install failed, fall through to PATH and common paths
+
+ # Check system PATH early - this covers Nix, distro packages, and other standalone installs.
+ # We verify the binary is functional to avoid picking up broken rustup proxy symlinks.
+ path_result = shutil.which("rust-analyzer")
+ if path_result and os.path.isfile(path_result) and os.access(path_result, os.X_OK):
+ if RustAnalyzer.DependencyProvider._is_rust_analyzer_functional(path_result):
+ return path_result
# Determine platform-specific binary name and paths
is_windows = platform.system() == "Windows"
binary_name = "rust-analyzer.exe" if is_windows else "rust-analyzer"
- # Fallback to common installation locations (only used if rustup not available)
+ # Fallback to common installation locations
common_paths: list[str | None] = []
if is_windows:
@@ -129,11 +149,6 @@ class RustAnalyzer(SolidLanguageServer):
if path and os.path.isfile(path) and os.access(path, os.X_OK):
return path
- # Last resort: check system PATH (can pick up incorrect aliases, hence checked last)
- path_result = shutil.which("rust-analyzer")
- if path_result and os.path.isfile(path_result) and os.access(path_result, os.X_OK):
- return path_result
-
# Provide helpful error message with all searched locations
searched = [p for p in common_paths if p]
install_instructions = [
diff --git a/src/solidlsp/language_servers/solidity_language_server.py b/src/solidlsp/language_servers/solidity_language_server.py
new file mode 100644
index 00000000..70c60161
--- /dev/null
+++ b/src/solidlsp/language_servers/solidity_language_server.py
@@ -0,0 +1,220 @@
+"""
+Provides Solidity-specific instantiation of the LanguageServer class using
+the Nomic Foundation Solidity Language Server (@nomicfoundation/solidity-language-server).
+"""
+
+import glob
+import logging
+import os
+import pathlib
+import shutil
+import threading
+from time import sleep
+from typing import Any
+
+from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
+from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
+from solidlsp.ls_config import LanguageServerConfig
+from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
+from solidlsp.settings import SolidLSPSettings
+
+log = logging.getLogger(__name__)
+
+
+class SolidityLanguageServer(SolidLanguageServer):
+ """
+ Provides Solidity-specific instantiation of the LanguageServer class using
+ the Nomic Foundation Solidity Language Server (@nomicfoundation/solidity-language-server).
+ Supports go-to-definition, find references, document symbols, hover, and diagnostics.
+ Requires Node.js and npm to be installed.
+ """
+
+ @staticmethod
+ def _determine_log_level(line: str) -> int:
+ """Suppress known non-critical stderr output from the Solidity language server."""
+ line_lower = line.lower()
+ if any(
+ [
+ "telemetry" in line_lower,
+ "could not find" in line_lower and "hardhat" in line_lower,
+ "no workspaceroot" in line_lower,
+ ]
+ ):
+ return logging.DEBUG
+ return SolidLanguageServer._determine_log_level(line)
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
+ """
+ Creates a SolidityLanguageServer instance. Not meant to be instantiated directly.
+ Use LanguageServer.create() instead.
+ """
+ super().__init__(
+ config,
+ repository_root_path,
+ None,
+ "solidity",
+ solidlsp_settings,
+ )
+
+ def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
+ return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
+
+ class DependencyProvider(LanguageServerDependencyProviderSinglePath):
+ def _get_or_install_core_dependency(self) -> str:
+ """
+ Install @nomicfoundation/solidity-language-server via npm and return the
+ path to the solidity-language-server executable.
+ """
+ is_node_installed = shutil.which("node") is not None
+ assert is_node_installed, "node is not installed or isn't in PATH. Please install Node.js and try again."
+ is_npm_installed = shutil.which("npm") is not None
+ assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
+
+ deps = RuntimeDependencyCollection(
+ [
+ RuntimeDependency(
+ id="solidity-language-server",
+ description="Nomic Foundation Solidity Language Server",
+ command="npm install --prefix ./ @nomicfoundation/solidity-language-server@0.8.4",
+ platform_id="any",
+ ),
+ ]
+ )
+
+ solidity_ls_dir = os.path.join(self._ls_resources_dir, "solidity-lsp")
+ solidity_executable_path = os.path.join(solidity_ls_dir, "node_modules", ".bin", "nomicfoundation-solidity-language-server")
+
+ if os.name == "nt":
+ solidity_executable_path += ".cmd"
+
+ if not os.path.exists(solidity_executable_path):
+ log.info(f"Solidity Language Server executable not found at {solidity_executable_path}. Installing...")
+ deps.install(solidity_ls_dir)
+ log.info("Solidity language server dependencies installed successfully.")
+
+ if not os.path.exists(solidity_executable_path):
+ raise FileNotFoundError(
+ f"nomicfoundation-solidity-language-server executable not found at {solidity_executable_path}. "
+ "Something went wrong with the installation."
+ )
+
+ return solidity_executable_path
+
+ def _create_launch_command(self, core_path: str) -> list[str]:
+ return [core_path, "--stdio"]
+
+ def is_ignored_dirname(self, dirname: str) -> bool:
+ return super().is_ignored_dirname(dirname) or dirname in {"artifacts", "cache", "typechain-types"}
+
+ @staticmethod
+ def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
+ """Return LSP InitializeParams for the Solidity language server."""
+ root_uri = pathlib.Path(repository_absolute_path).as_uri()
+ return { # type: ignore
+ "locale": "en",
+ "processId": os.getpid(),
+ "rootPath": repository_absolute_path,
+ "rootUri": root_uri,
+ "workspaceFolders": [
+ {
+ "uri": root_uri,
+ "name": os.path.basename(repository_absolute_path),
+ }
+ ],
+ "capabilities": {
+ "textDocument": {
+ "synchronization": {
+ "dynamicRegistration": True,
+ "didSave": True,
+ },
+ "completion": {
+ "dynamicRegistration": True,
+ "completionItem": {"snippetSupport": True},
+ },
+ "definition": {"dynamicRegistration": True},
+ "references": {"dynamicRegistration": True},
+ "documentSymbol": {
+ "dynamicRegistration": True,
+ "hierarchicalDocumentSymbolSupport": True,
+ "symbolKind": {"valueSet": list(range(1, 27))}, # type: ignore[arg-type]
+ },
+ "hover": {
+ "dynamicRegistration": True,
+ "contentFormat": ["markdown", "plaintext"], # type: ignore[list-item]
+ },
+ },
+ "workspace": {
+ "workspaceFolders": True,
+ "didChangeConfiguration": {"dynamicRegistration": True},
+ "symbol": {"dynamicRegistration": True},
+ },
+ },
+ "initializationOptions": {},
+ }
+
+ def _get_wait_time_for_cross_file_referencing(self) -> float:
+ # Small buffer for any post-indexing analysis the LSP performs after file-indexed events.
+ return 3.0
+
+ def _start_server(self) -> None:
+ """Start the Solidity language server and wait for project indexing to finish."""
+
+ def do_nothing(params: Any) -> None:
+ return
+
+ def register_capability_handler(params: Any) -> None:
+ return
+
+ def window_log_message(msg: dict) -> None:
+ log.info(f"LSP: window/logMessage: {msg}")
+
+ # Count .sol files in the project to know when indexing is complete.
+ sol_files = glob.glob(os.path.join(self.repository_root_path, "**", "*.sol"), recursive=True)
+ expected_count = len(sol_files)
+ indexed_count = [0]
+ all_indexed = threading.Event()
+
+ def on_file_indexed(params: Any) -> None:
+ indexed_count[0] += 1
+ uri = (params or {}).get("uri", "")
+ log.debug(f"Solidity LSP: file indexed ({indexed_count[0]}/{expected_count}): {uri}")
+ if indexed_count[0] >= expected_count:
+ all_indexed.set()
+
+ 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.server.on_notification("custom/file-indexed", on_file_indexed)
+
+ log.info("Starting Solidity language server process")
+ self.server.start()
+
+ initialize_params = self._get_initialize_params(self.repository_root_path)
+ log.debug("Sending initialize request to Solidity language server")
+ init_response = self.server.send.initialize(initialize_params)
+ log.debug(f"Received initialize response from Solidity server: {init_response}")
+
+ if "documentSymbolProvider" in init_response.get("capabilities", {}):
+ log.debug("Solidity server supports document symbols")
+ else:
+ log.warning("Solidity server does not report document symbol support")
+
+ self.server.notify.initialized({})
+
+ if expected_count > 0:
+ log.info(f"Waiting for Solidity LSP to index {expected_count} .sol file(s)…")
+ completed = all_indexed.wait(timeout=60)
+ if completed:
+ log.info(f"Solidity LSP indexing complete ({indexed_count[0]}/{expected_count} files indexed)")
+ else:
+ log.warning(
+ f"Solidity LSP indexing timed out ({indexed_count[0]}/{expected_count} files indexed). "
+ "Waiting additional 30s for slow environments (e.g., CI)."
+ )
+ sleep(30)
+ log.info(f"Additional wait complete ({indexed_count[0]}/{expected_count} files indexed)")
+ else:
+ log.info("No .sol files found; skipping indexing wait")
+
+ log.info("Solidity language server initialization complete")
diff --git a/src/solidlsp/language_servers/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server.py
index 01ec205e..4c428986 100644
--- a/src/solidlsp/language_servers/typescript_language_server.py
+++ b/src/solidlsp/language_servers/typescript_language_server.py
@@ -67,6 +67,10 @@ class TypeScriptLanguageServer(SolidLanguageServer):
- typescript_language_server_version: Version of typescript-language-server to install (default: "5.1.3")
"""
+ # Safety timeout for $/progress-based indexing wait. Normally the event fires
+ # well within this window; the timeout is only hit if the server never sends progress.
+ INDEXING_PROGRESS_TIMEOUT = 15.0 if os.name == "nt" else 10.0
+
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
@@ -81,6 +85,29 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self.server_ready = threading.Event()
self.initialize_searcher_command_available = threading.Event()
+ # Progress tracking for $/progress notifications (project indexing, etc.)
+ self._progress_lock = threading.Lock()
+ self._active_progress_tokens: set[str] = set()
+ self._indexing_complete = threading.Event()
+ self._indexing_complete.set() # Initially set (no active work)
+
+ def wait_for_indexing(self, timeout: float) -> bool:
+ """Block until all $/progress tokens complete.
+
+ :param timeout: Maximum seconds to wait.
+ :return: True if indexing completed, False on timeout.
+ """
+ return self._indexing_complete.wait(timeout=timeout)
+
+ def expect_indexing(self) -> None:
+ """Signal that new files are about to be opened and async indexing should be awaited.
+
+ Clears the internal indexing-complete event so that a subsequent
+ :meth:`wait_for_indexing` call blocks until all $/progress tokens
+ complete (or the timeout expires).
+ """
+ self._indexing_complete.clear()
+
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
@@ -114,9 +141,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
PlatformId.WIN_x64,
PlatformId.WIN_arm64,
]
- assert (
- platform_id in valid_platforms
- ), f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"
+ assert platform_id in valid_platforms, (
+ f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"
+ )
# Get version settings from ls_specific_settings or use defaults
language_specific_config = self._custom_settings
@@ -217,6 +244,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
"didChangeConfiguration": {"dynamicRegistration": True},
"symbol": {"dynamicRegistration": True},
},
+ "window": {
+ "workDoneProgress": True, # Enables $/progress notifications for project loading
+ },
},
"processId": os.getpid(),
"rootPath": repository_absolute_path,
@@ -242,6 +272,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
await lsp.request_references(...)
# Shutdown the LanguageServer on exit from scope
# LanguageServer has been shutdown
+ ```
"""
def register_capability_handler(params: dict) -> None:
@@ -263,19 +294,65 @@ class TypeScriptLanguageServer(SolidLanguageServer):
def window_log_message(msg: dict) -> None:
log.info(f"LSP: window/logMessage: {msg}")
- def check_experimental_status(params: dict) -> None:
+ def handle_typescript_version(params: dict) -> None:
"""
- Also listen for experimental/serverStatus as a backup signal
+ The $/typescriptVersion notification is sent by typescript-language-server
+ once tsserver has loaded and reported its version. This is a reliable
+ signal that tsserver is running and responsive.
"""
- if params.get("quiescent") == True:
- self.server_ready.set()
+ log.info(f"TypeScript server version notification received: {params}")
+ self.server_ready.set()
+
+ def work_done_progress_create(params: dict) -> dict:
+ """Handle window/workDoneProgress/create: the server is about to report async progress.
+
+ Clear the indexing-complete event so callers waiting on it will block until
+ all progress tokens finish. This is sent by typescript-language-server when
+ tsserver starts processing files (e.g. "Initializing JS/TS language features...").
+ """
+ token = str(params.get("token", ""))
+ log.debug(f"TypeScript LSP workDoneProgress/create: token={token!r}")
+ with self._progress_lock:
+ self._active_progress_tokens.add(token)
+ self._indexing_complete.clear()
+ return {}
+
+ def progress_handler(params: dict) -> None:
+ """Track $/progress begin/end to detect when all async work finishes.
+
+ typescript-language-server sends $/progress for project loading operations
+ like "Initializing JS/TS language features...". When all progress tokens
+ complete (kind='end'), _indexing_complete is set.
+ """
+ token = str(params.get("token", ""))
+ value = params.get("value", {})
+ kind = value.get("kind")
+ if kind == "begin":
+ title = value.get("title", "")
+ log.info(f"TypeScript LSP progress [{token}]: started - {title}")
+ with self._progress_lock:
+ self._active_progress_tokens.add(token)
+ self._indexing_complete.clear()
+ elif kind == "report":
+ pct = value.get("percentage")
+ msg = value.get("message", "")
+ pct_str = f" ({pct}%)" if pct is not None else ""
+ log.debug(f"TypeScript LSP progress [{token}]: {msg}{pct_str}")
+ elif kind == "end":
+ msg = value.get("message", "")
+ log.info(f"TypeScript LSP progress [{token}]: ended - {msg}")
+ with self._progress_lock:
+ self._active_progress_tokens.discard(token)
+ if not self._active_progress_tokens:
+ self._indexing_complete.set()
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
- self.server.on_notification("$/progress", do_nothing)
+ self.server.on_request("window/workDoneProgress/create", work_done_progress_create)
+ self.server.on_notification("$/progress", progress_handler)
+ self.server.on_notification("$/typescriptVersion", handle_typescript_version)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
- self.server.on_notification("experimental/serverStatus", check_experimental_status)
log.info("Starting TypeScript server process")
self.server.start()
@@ -295,13 +372,26 @@ class TypeScriptLanguageServer(SolidLanguageServer):
}
self.server.notify.initialized({})
- if self.server_ready.wait(timeout=1.0):
+ if self.server_ready.wait(timeout=10.0):
log.info("TypeScript server is ready")
else:
log.info("Timeout waiting for TypeScript server to become ready, proceeding anyway")
# Fallback: assume server is ready after timeout
self.server_ready.set()
+ # Wait for any async project loading to complete.
+ # typescript-language-server may send $/progress for "Initializing JS/TS
+ # language features…" after initialized. If no progress is sent,
+ # _indexing_complete stays SET and wait() returns immediately.
+ log.info("Waiting for TypeScript project indexing to complete (if async)...")
+ if self.wait_for_indexing(timeout=self.INDEXING_PROGRESS_TIMEOUT):
+ log.info("TypeScript project indexing complete")
+ else:
+ log.warning(
+ "TypeScript project indexing did not complete within %.0fs; proceeding anyway",
+ self.INDEXING_PROGRESS_TIMEOUT,
+ )
+
@override
def _get_wait_time_for_cross_file_referencing(self) -> float:
return 2
diff --git a/src/solidlsp/language_servers/vts_language_server.py b/src/solidlsp/language_servers/vts_language_server.py
index e60eda1a..90fc18ff 100644
--- a/src/solidlsp/language_servers/vts_language_server.py
+++ b/src/solidlsp/language_servers/vts_language_server.py
@@ -155,6 +155,7 @@ class VtsLanguageServer(SolidLanguageServer):
await lsp.request_references(...)
# Shutdown the LanguageServer on exit from scope
# LanguageServer has been shutdown
+ ```
"""
def register_capability_handler(params: dict) -> None:
diff --git a/src/solidlsp/language_servers/vue_language_server.py b/src/solidlsp/language_servers/vue_language_server.py
index 61c3ec34..1314bec1 100644
--- a/src/solidlsp/language_servers/vue_language_server.py
+++ b/src/solidlsp/language_servers/vue_language_server.py
@@ -134,8 +134,6 @@ class VueLanguageServer(SolidLanguageServer):
TS_SERVER_READY_TIMEOUT = 5.0
VUE_SERVER_READY_TIMEOUT = 3.0
- # Windows requires more time due to slower I/O and process operations.
- VUE_INDEXING_WAIT_TIME = 4.0 if os.name == "nt" else 2.0
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
vue_lsp_executable_path, self.tsdk_path, self._ts_ls_cmd = self._setup_runtime_dependencies(config, solidlsp_settings)
@@ -204,6 +202,11 @@ class VueLanguageServer(SolidLanguageServer):
vue_files = self._find_all_vue_files()
log.debug(f"Found {len(vue_files)} .vue files to index")
+ # Prepare the TS server to track new $/progress notifications triggered
+ # by the didOpen calls below. Must happen BEFORE opening files to avoid
+ # a race where progress begins and ends before we start waiting.
+ self._ts_server.expect_indexing()
+
for vue_file in vue_files:
try:
with self._ts_server.open_file(vue_file) as file_buffer:
@@ -213,13 +216,23 @@ class VueLanguageServer(SolidLanguageServer):
log.debug(f"Failed to open {vue_file} on TS server: {e}")
self._vue_files_indexed = True
- log.info("Vue file indexing on TypeScript server complete")
+ log.info("Vue file indexing on TypeScript server complete, waiting for TS server to finish processing")
- sleep(self._get_vue_indexing_wait_time())
- log.debug("Wait period after Vue file indexing complete")
+ self._wait_for_ts_indexing_complete()
- def _get_vue_indexing_wait_time(self) -> float:
- return self.VUE_INDEXING_WAIT_TIME
+ def _wait_for_ts_indexing_complete(self) -> None:
+ """Wait for the companion TypeScript server to finish processing opened Vue files.
+
+ Uses the $/progress tracking in TypeScriptLanguageServer: after Vue files are
+ opened, tsserver sends "Initializing JS/TS language features…" progress.
+ We wait for all progress tokens to complete, with a timeout fallback.
+ """
+ assert self._ts_server is not None
+ timeout = TypeScriptLanguageServer.INDEXING_PROGRESS_TIMEOUT
+ if self._ts_server.wait_for_indexing(timeout=timeout):
+ log.info("TypeScript server finished indexing Vue files (signaled via $/progress)")
+ else:
+ log.warning(f"Timeout ({timeout}s) waiting for TypeScript server to finish indexing Vue files, proceeding anyway")
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None:
uri = PathUtils.path_to_uri(os.path.join(self.repository_root_path, relative_file_path))
diff --git a/src/solidlsp/language_servers/zls.py b/src/solidlsp/language_servers/zls.py
index 8f8a86e8..368f57eb 100644
--- a/src/solidlsp/language_servers/zls.py
+++ b/src/solidlsp/language_servers/zls.py
@@ -70,8 +70,7 @@ class ZigLanguageServer(SolidLanguageServer):
# Check for Windows and provide error message
if platform.system() == "Windows":
raise RuntimeError(
- "Windows is not supported by ZLS in this integration. "
- "Cross-file references don't work reliably on Windows. Reason unknown."
+ "Windows is not supported by ZLS in this integration. Cross-file references don't work reliably on Windows. Reason unknown."
)
zig_version = ZigLanguageServer._get_zig_version()
diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py
index 25208f06..e37a1abc 100644
--- a/src/solidlsp/ls.py
+++ b/src/solidlsp/ls.py
@@ -477,9 +477,7 @@ class SolidLanguageServer(ABC):
self.language = Language(language_id)
# initialise symbol caches
- self.cache_dir = (
- Path(self.repository_root_path) / self._solidlsp_settings.project_data_relative_path / self.CACHE_FOLDER_NAME / self.language_id
- )
+ self.cache_dir = Path(self._solidlsp_settings.project_data_path) / self.CACHE_FOLDER_NAME / self.language_id
self.cache_dir.mkdir(parents=True, exist_ok=True)
# * raw document symbols cache
self._ls_specific_raw_document_symbols_cache_version = cache_version_raw_document_symbols
@@ -819,7 +817,7 @@ class SolidLanguageServer(ABC):
Delete text between the given start and end positions in the given file and return the deleted text.
"""
if not self.server_started:
- log.error("insert_text_at_position called before Language Server started")
+ log.error("delete_text_between_positions called before Language Server started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
@@ -1106,12 +1104,12 @@ 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): # type: ignore
+ for _ in range(30):
response = self.server.send.completion(completion_params)
if isinstance(response, list):
response = {"items": response, "isIncomplete": False}
- num_retries += 1
+ if response is None or not response["isIncomplete"]: # type: ignore
+ break
# TODO: Understand how to appropriately handle `isIncomplete`
if response is None or (response["isIncomplete"] and not allow_incomplete): # type: ignore
diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py
index 935ce98d..2633e62f 100644
--- a/src/solidlsp/ls_config.py
+++ b/src/solidlsp/ls_config.py
@@ -53,8 +53,14 @@ class Language(str, Enum):
BASH = "bash"
ZIG = "zig"
LUA = "lua"
+ LUAU = "luau"
+ """Luau Language Server for Roblox's Luau language (typed Lua 5.1 superset).
+ Uses luau-lsp by JohnnyMorganz. Automatically downloads the binary if not found.
+ Supports .luau files. Configure via .luaurc in the project root.
+ """
NIX = "nix"
ERLANG = "erlang"
+ OCAML = "ocaml"
AL = "al"
FSHARP = "fsharp"
REGO = "rego"
@@ -62,6 +68,7 @@ class Language(str, Enum):
JULIA = "julia"
FORTRAN = "fortran"
HASKELL = "haskell"
+ LEAN4 = "lean4"
GROOVY = "groovy"
VUE = "vue"
POWERSHELL = "powershell"
@@ -105,11 +112,29 @@ class Language(str, Enum):
"""TOML language server using Taplo.
Supports TOML validation, formatting, and schema support.
"""
+ HLSL = "hlsl"
+ """Shader language server using shader-language-server (antaalt/shader-sense).
+ Supports .hlsl, .hlsli, .fx, .fxh, .cginc, .compute, .shader, .glsl, .vert, .frag, .geom, .tesc, .tese, .comp, .wgsl files.
+ Automatically downloads shader-language-server binary.
+ """
SYSTEMVERILOG = "systemverilog"
"""SystemVerilog language server using verible-verilog-ls.
Supports .sv, .svh, .v, .vh files.
Automatically downloads verible binary.
"""
+ SOLIDITY = "solidity"
+ """Solidity language server using the Nomic Foundation Solidity Language Server
+ (@nomicfoundation/solidity-language-server).
+ Supports .sol files. Provides go-to-definition, find references, document symbols,
+ hover, and diagnostics. Requires Node.js and npm.
+ Works best with a foundry.toml or hardhat.config.js in the project root.
+ """
+ ANSIBLE = "ansible"
+ """Ansible language server (experimental) using @ansible/ansible-language-server.
+ Supports *.yaml and *.yml files (same extensions as YAML, hence experimental).
+ Must be explicitly specified in project.yml. Requires Node.js and npm.
+ Requires ``ansible`` in PATH for full functionality.
+ """
@classmethod
def iter_all(cls, include_experimental: bool = False) -> Iterable[Self]:
@@ -126,6 +151,7 @@ class Language(str, Enum):
in the project.yml configuration.
"""
return self in {
+ self.ANSIBLE,
self.TYPESCRIPT_VTS,
self.PYTHON_JEDI,
self.CSHARP_OMNISHARP,
@@ -136,6 +162,7 @@ class Language(str, Enum):
self.TOML,
self.GROOVY,
self.CPP_CCLS,
+ self.SOLIDITY,
}
def __str__(self) -> str:
@@ -214,10 +241,14 @@ class Language(str, Enum):
return FilenameMatcher("*.zig", "*.zon")
case self.LUA:
return FilenameMatcher("*.lua")
+ case self.LUAU:
+ return FilenameMatcher("*.luau")
case self.NIX:
return FilenameMatcher("*.nix")
case self.ERLANG:
return FilenameMatcher("*.erl", "*.hrl", "*.escript", "*.config", "*.app", "*.app.src")
+ case self.OCAML:
+ return FilenameMatcher("*.ml", "*.mli", "*.re", "*.rei")
case self.AL:
return FilenameMatcher("*.al", "*.dal")
case self.FSHARP:
@@ -236,6 +267,8 @@ class Language(str, Enum):
)
case self.HASKELL:
return FilenameMatcher("*.hs", "*.lhs")
+ case self.LEAN4:
+ return FilenameMatcher("*.lean")
case self.VUE:
path_patterns = ["*.vue"]
for prefix in ["c", "m", ""]:
@@ -251,8 +284,30 @@ class Language(str, Enum):
return FilenameMatcher("*.groovy", "*.gvy")
case self.MATLAB:
return FilenameMatcher("*.m", "*.mlx", "*.mlapp")
+ case self.HLSL:
+ return FilenameMatcher(
+ "*.hlsl",
+ "*.hlsli",
+ "*.fx",
+ "*.fxh",
+ "*.cginc",
+ "*.compute",
+ "*.shader",
+ "*.glsl",
+ "*.vert",
+ "*.frag",
+ "*.geom",
+ "*.tesc",
+ "*.tese",
+ "*.comp",
+ "*.wgsl",
+ )
case self.SYSTEMVERILOG:
return FilenameMatcher("*.sv", "*.svh", "*.v", "*.vh")
+ case self.SOLIDITY:
+ return FilenameMatcher("*.sol")
+ case self.ANSIBLE:
+ return FilenameMatcher("*.yaml", "*.yml")
case _:
raise ValueError(f"Unhandled language: {self}")
@@ -378,10 +433,20 @@ class Language(str, Enum):
from solidlsp.language_servers.lua_ls import LuaLanguageServer
return LuaLanguageServer
+
+ case self.LUAU:
+ from solidlsp.language_servers.luau_lsp import LuauLanguageServer
+
+ return LuauLanguageServer
+
case self.ERLANG:
from solidlsp.language_servers.erlang_language_server import ErlangLanguageServer
return ErlangLanguageServer
+ case self.OCAML:
+ from solidlsp.language_servers.ocaml_lsp_server import OcamlLanguageServer
+
+ return OcamlLanguageServer
case self.AL:
from solidlsp.language_servers.al_language_server import ALLanguageServer
@@ -414,6 +479,10 @@ class Language(str, Enum):
from solidlsp.language_servers.haskell_language_server import HaskellLanguageServer
return HaskellLanguageServer
+ case self.LEAN4:
+ from solidlsp.language_servers.lean4_language_server import Lean4LanguageServer
+
+ return Lean4LanguageServer
case self.FSHARP:
from solidlsp.language_servers.fsharp_language_server import FSharpLanguageServer
@@ -434,10 +503,22 @@ class Language(str, Enum):
from solidlsp.language_servers.matlab_language_server import MatlabLanguageServer
return MatlabLanguageServer
+ case self.HLSL:
+ from solidlsp.language_servers.hlsl_language_server import HlslLanguageServer
+
+ return HlslLanguageServer
case self.SYSTEMVERILOG:
from solidlsp.language_servers.systemverilog_server import SystemVerilogLanguageServer
return SystemVerilogLanguageServer
+ case self.SOLIDITY:
+ from solidlsp.language_servers.solidity_language_server import SolidityLanguageServer
+
+ return SolidityLanguageServer
+ case self.ANSIBLE:
+ from solidlsp.language_servers.ansible_language_server import AnsibleLanguageServer
+
+ return AnsibleLanguageServer
case _:
raise ValueError(f"Unhandled language: {self}")
diff --git a/src/solidlsp/ls_types.py b/src/solidlsp/ls_types.py
index 0eafcfea..78446bc5 100644
--- a/src/solidlsp/ls_types.py
+++ b/src/solidlsp/ls_types.py
@@ -7,13 +7,18 @@ from __future__ import annotations
from enum import Enum, IntEnum
from typing import TYPE_CHECKING, NotRequired, Union
+from sensai.util.helper import mark_used
from typing_extensions import TypedDict
-from solidlsp.lsp_protocol_handler.lsp_types import DiagnosticSeverity
+from solidlsp.lsp_protocol_handler.lsp_types import DiagnosticSeverity, SymbolKind
if TYPE_CHECKING:
from .ls import SymbolBody
+# a lot of code relied on a previously duplicated SymbolKind definition here.
+# This line is kept to avoid breaking downstream imports
+mark_used(SymbolKind)
+
URI = str
DocumentUri = str
@@ -147,39 +152,6 @@ class CompletionItem(TypedDict):
about this item, like type or symbol information. """
-class SymbolKind(IntEnum):
- """A symbol kind."""
-
- # TODO: This is a duplicate of SymbolKind in lsp_types.
-
- File = 1
- Module = 2
- Namespace = 3
- Package = 4
- Class = 5
- Method = 6
- Property = 7
- Field = 8
- Constructor = 9
- Enum = 10
- Interface = 11
- Function = 12
- Variable = 13
- Constant = 14
- String = 15
- Number = 16
- Boolean = 17
- Array = 18
- Object = 19
- Key = 20
- Null = 21
- EnumMember = 22
- Struct = 23
- Event = 24
- Operator = 25
- TypeParameter = 26
-
-
class SymbolTag(IntEnum):
"""Symbol tags are extra annotations that tweak the rendering of a symbol.
diff --git a/src/solidlsp/lsp_protocol_handler/lsp_types.py b/src/solidlsp/lsp_protocol_handler/lsp_types.py
index 4c1753b5..ffb6135e 100644
--- a/src/solidlsp/lsp_protocol_handler/lsp_types.py
+++ b/src/solidlsp/lsp_protocol_handler/lsp_types.py
@@ -1,36 +1,16 @@
-# Code generated. DO NOT EDIT.
+# The code in this module is mostly autogenerated using https://github.com/predragnikolic/OLSP
+# To update it for higher LSP versions, autogenerate a new version in a different file and merge manually.
+
# LSP v3.17.0
-# TODO: Look into use of https://pypi.org/project/ts2python/ to generate the types for https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
+# Note by author of the autogenerator: Look into use of https://pypi.org/project/ts2python/ to generate the types f
+# or https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
"""
This file provides the Python types corresponding to the Typescript types defined in the language server protocol.
-This file is obtained from https://github.com/predragnikolic/OLSP under the MIT License with the following terms:
-
-MIT License
-
-Copyright (c) 2023 Предраг Николић
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
"""
from enum import Enum, IntEnum, IntFlag
-from typing import Literal, NotRequired, Union
+from typing import Any, Literal, NotRequired, Union
from typing_extensions import TypedDict
@@ -166,6 +146,8 @@ class FoldingRangeKind(Enum):
class SymbolKind(IntEnum):
"""A symbol kind."""
+ Unknown = 0
+ """Not an LSP type, we use it to represent symbols that don't have a known kind."""
File = 1
Module = 2
Namespace = 3
@@ -197,11 +179,8 @@ class SymbolKind(IntEnum):
TypeParameter = 26
@classmethod
- def from_int(cls, value: int) -> "SymbolKind":
- for symbol_kind in cls:
- if symbol_kind.value == value:
- return symbol_kind
- raise ValueError(f"Invalid symbol kind: {value}")
+ def _missing_(cls, value: Any) -> "SymbolKind":
+ return cls.Unknown
class SymbolTag(IntEnum):
diff --git a/src/solidlsp/settings.py b/src/solidlsp/settings.py
index ad77eb22..db975945 100644
--- a/src/solidlsp/settings.py
+++ b/src/solidlsp/settings.py
@@ -22,11 +22,11 @@ class SolidLSPSettings:
"""
Path to the directory in which to store global Solid-LSP data (which is not project-specific)
"""
- project_data_relative_path: str = ".solidlsp"
+ project_data_path: str = ""
"""
- Relative path within each project directory where Solid-LSP can store project-specific data, e.g. cache files.
- For instance, if this is ".solidlsp" and the project is located at "/home/user/myproject",
- then Solid-LSP will store project-specific data in "/home/user/myproject/.solidlsp".
+ Absolute path to a directory where Solid-LSP can store project-specific data, e.g. cache files.
+ For instance, if this is "/home/user/myproject/.solidlsp",
+ then Solid-LSP will store project-specific data (e.g. caches) in that directory.
"""
ls_specific_settings: dict["Language", dict[str, Any]] = field(default_factory=dict)
"""
diff --git a/src/solidlsp/util/subprocess_util.py b/src/solidlsp/util/subprocess_util.py
index c0735f0c..175ae1e9 100644
--- a/src/solidlsp/util/subprocess_util.py
+++ b/src/solidlsp/util/subprocess_util.py
@@ -1,4 +1,5 @@
import platform
+import shlex
import subprocess
@@ -15,8 +16,14 @@ def subprocess_kwargs() -> dict:
def quote_arg(arg: str) -> str:
"""
- Adds quotes around an argument if it contains spaces.
+ Quotes a shell argument to prevent interpretation of metacharacters.
+
+ Uses :func:`shlex.quote` on POSIX systems for proper escaping of all
+ shell-special characters. On Windows, wraps arguments containing spaces
+ in double quotes (Windows shell does not interpret single-quoted strings).
"""
- if " " not in arg:
- return arg
- return f'"{arg}"'
+ if platform.system() == "Windows":
+ if " " not in arg:
+ return arg
+ return f'"{arg}"'
+ return shlex.quote(arg)
diff --git a/test/conftest.py b/test/conftest.py
index 565d40b7..f8981251 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -10,7 +10,7 @@ from typing import Any
import pytest
from sensai.util.logging import configure
-from serena.config.serena_config import SerenaPaths
+from serena.config.serena_config import SerenaConfig, SerenaPaths
from serena.constants import SERENA_MANAGED_DIR_NAME
from serena.project import Project
from serena.util.file_system import GitignoreParser
@@ -69,12 +69,13 @@ def _create_ls(
trace_lsp_communication=trace_lsp_communication,
)
effective_solidlsp_dir = solidlsp_dir if solidlsp_dir is not None else SerenaPaths().serena_user_home_dir
+ project_data_path = os.path.join(repo_path, SERENA_MANAGED_DIR_NAME)
return SolidLanguageServer.create(
config,
repo_path,
solidlsp_settings=SolidLSPSettings(
solidlsp_dir=effective_solidlsp_dir,
- project_data_relative_path=SERENA_MANAGED_DIR_NAME,
+ project_data_path=project_data_path,
ls_specific_settings=ls_specific_settings or {},
),
)
@@ -115,9 +116,13 @@ def start_default_ls_context(language: Language) -> Iterator[SolidLanguageServer
yield ls
-def _create_default_project(language: Language) -> Project:
- repo_path = str(get_repo_path(language))
- return Project.load(repo_path, serena_config=None)
+def create_default_serena_config():
+ return SerenaConfig(gui_log_window=False, web_dashboard=False)
+
+
+def _create_default_project(language: Language, repo_root_override: str | None = None) -> Project:
+ repo_path = str(get_repo_path(language)) if repo_root_override is None else repo_root_override
+ return Project.load(repo_path, serena_config=create_default_serena_config())
@pytest.fixture(scope="session")
@@ -173,8 +178,18 @@ def language_server(request: LanguageParamRequest):
yield ls
+@contextmanager
+def project_context(language: Language, repo_root_override: str | None = None) -> Iterator[Project]:
+ """Context manager that creates a Project for the specified language and ensures proper cleanup."""
+ project = _create_default_project(language, repo_root_override)
+ try:
+ yield project
+ finally:
+ project.shutdown(timeout=5)
+
+
@pytest.fixture(scope="module")
-def project(request: LanguageParamRequest):
+def project(request: LanguageParamRequest, repo_root_override: str | None = None) -> Iterator[Project]:
"""Create a Project for the specified language.
This fixture requires a language parameter via pytest.mark.parametrize:
@@ -198,11 +213,26 @@ def project(request: LanguageParamRequest):
"""
if not hasattr(request, "param"):
raise ValueError("Language parameter must be provided via pytest.mark.parametrize")
-
language = request.param
- project = _create_default_project(language)
- yield project
- project.shutdown(timeout=5)
+ with project_context(language, repo_root_override) as project:
+ yield project
+
+
+@contextmanager
+def project_with_ls_context(language: Language, repo_root_override: str | None = None) -> Iterator[Project]:
+ """Context manager that creates a Project with an active language server for the specified language."""
+ with project_context(language, repo_root_override) as project:
+ project.create_language_server_manager()
+ yield project
+
+
+@pytest.fixture(scope="module")
+def project_with_ls(request: LanguageParamRequest) -> Iterator[Project]:
+ if not hasattr(request, "param"):
+ raise ValueError("Language parameter must be provided via pytest.mark.parametrize")
+ language = request.param
+ with project_with_ls_context(language) as project:
+ yield project
is_ci = os.getenv("CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true"
diff --git a/test/resources/repos/ansible/test_repo/inventory/hosts.yml b/test/resources/repos/ansible/test_repo/inventory/hosts.yml
new file mode 100644
index 00000000..616cd917
--- /dev/null
+++ b/test/resources/repos/ansible/test_repo/inventory/hosts.yml
@@ -0,0 +1,13 @@
+---
+all:
+ children:
+ webservers:
+ hosts:
+ web1.example.com:
+ ansible_host: 192.168.1.10
+ web2.example.com:
+ ansible_host: 192.168.1.11
+ dbservers:
+ hosts:
+ db1.example.com:
+ ansible_host: 192.168.1.20
diff --git a/test/resources/repos/ansible/test_repo/playbook.yml b/test/resources/repos/ansible/test_repo/playbook.yml
new file mode 100644
index 00000000..37a4e978
--- /dev/null
+++ b/test/resources/repos/ansible/test_repo/playbook.yml
@@ -0,0 +1,31 @@
+---
+- name: Configure web servers
+ hosts: webservers
+ become: true
+ vars:
+ http_port: 80
+ max_connections: 100
+
+ tasks:
+ - name: Install nginx
+ ansible.builtin.package:
+ name: nginx
+ state: present
+
+ - name: Start nginx service
+ ansible.builtin.service:
+ name: nginx
+ state: started
+ enabled: true
+
+ - name: Copy config file
+ ansible.builtin.template:
+ src: nginx.conf.j2
+ dest: /etc/nginx/nginx.conf
+ notify: Restart nginx
+
+ handlers:
+ - name: Restart nginx
+ ansible.builtin.service:
+ name: nginx
+ state: restarted
diff --git a/test/resources/repos/ansible/test_repo/roles/common/defaults/main.yml b/test/resources/repos/ansible/test_repo/roles/common/defaults/main.yml
new file mode 100644
index 00000000..83723e36
--- /dev/null
+++ b/test/resources/repos/ansible/test_repo/roles/common/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+nginx_listen_port: 80
+deploy_user: deploy
diff --git a/test/resources/repos/ansible/test_repo/roles/common/handlers/main.yml b/test/resources/repos/ansible/test_repo/roles/common/handlers/main.yml
new file mode 100644
index 00000000..aa567e7c
--- /dev/null
+++ b/test/resources/repos/ansible/test_repo/roles/common/handlers/main.yml
@@ -0,0 +1,4 @@
+---
+- name: Restart common services
+ ansible.builtin.debug:
+ msg: "Restarting common services"
diff --git a/test/resources/repos/ansible/test_repo/roles/common/tasks/main.yml b/test/resources/repos/ansible/test_repo/roles/common/tasks/main.yml
new file mode 100644
index 00000000..5ce8ceae
--- /dev/null
+++ b/test/resources/repos/ansible/test_repo/roles/common/tasks/main.yml
@@ -0,0 +1,19 @@
+---
+- name: Update package cache
+ ansible.builtin.package:
+ update_cache: true
+
+- name: Install common packages
+ ansible.builtin.package:
+ name: "{{ item }}"
+ state: present
+ loop:
+ - curl
+ - git
+ - vim
+
+- name: Create deploy user
+ ansible.builtin.user:
+ name: deploy
+ state: present
+ shell: /bin/bash
diff --git a/test/resources/repos/hlsl/test_repo/common.hlsl b/test/resources/repos/hlsl/test_repo/common.hlsl
new file mode 100644
index 00000000..1da49f84
--- /dev/null
+++ b/test/resources/repos/hlsl/test_repo/common.hlsl
@@ -0,0 +1,29 @@
+#ifndef COMMON_HLSL
+#define COMMON_HLSL
+
+struct VertexInput
+{
+ float3 position : POSITION;
+ float3 normal : NORMAL;
+ float2 uv : TEXCOORD0;
+};
+
+struct VertexOutput
+{
+ float4 clipPos : SV_POSITION;
+ float3 worldNormal : TEXCOORD0;
+ float2 uv : TEXCOORD1;
+};
+
+float3 SafeNormalize(float3 v)
+{
+ float len = length(v);
+ return len > 0.0001 ? v / len : float3(0, 0, 0);
+}
+
+float Remap(float value, float fromMin, float fromMax, float toMin, float toMax)
+{
+ return toMin + (value - fromMin) * (toMax - toMin) / (fromMax - fromMin);
+}
+
+#endif // COMMON_HLSL
diff --git a/test/resources/repos/hlsl/test_repo/compute_test.hlsl b/test/resources/repos/hlsl/test_repo/compute_test.hlsl
new file mode 100644
index 00000000..f10531ef
--- /dev/null
+++ b/test/resources/repos/hlsl/test_repo/compute_test.hlsl
@@ -0,0 +1,26 @@
+#include "common.hlsl"
+
+RWTexture2D OutputTexture : register(u0);
+Texture2D InputTexture : register(t0);
+
+cbuffer ComputeParams : register(b0)
+{
+ uint2 TextureSize;
+ float BlurRadius;
+ float _Pad;
+};
+
+[numthreads(8, 8, 1)]
+void CSMain(uint3 id : SV_DispatchThreadID)
+{
+ if (id.x >= TextureSize.x || id.y >= TextureSize.y)
+ return;
+
+ float4 color = InputTexture[id.xy];
+ float3 remapped = float3(
+ Remap(color.r, 0.0, 1.0, 0.2, 0.8),
+ Remap(color.g, 0.0, 1.0, 0.2, 0.8),
+ Remap(color.b, 0.0, 1.0, 0.2, 0.8)
+ );
+ OutputTexture[id.xy] = float4(remapped, color.a);
+}
diff --git a/test/resources/repos/hlsl/test_repo/lighting.hlsl b/test/resources/repos/hlsl/test_repo/lighting.hlsl
new file mode 100644
index 00000000..2334b639
--- /dev/null
+++ b/test/resources/repos/hlsl/test_repo/lighting.hlsl
@@ -0,0 +1,37 @@
+#ifndef LIGHTING_HLSL
+#define LIGHTING_HLSL
+
+#include "common.hlsl"
+
+cbuffer LightingConstants : register(b0)
+{
+ float4x4 ViewProjection;
+ float3 LightDirection;
+ float LightIntensity;
+ float3 AmbientColor;
+ float _Padding;
+};
+
+float3 CalculateDiffuse(float3 normal, float3 lightDir, float3 albedo)
+{
+ float ndotl = max(dot(normal, -lightDir), 0.0);
+ return albedo * ndotl;
+}
+
+float3 CalculateSpecular(float3 normal, float3 lightDir, float3 viewDir, float shininess)
+{
+ float3 halfVec = SafeNormalize(-lightDir + viewDir);
+ float ndoth = max(dot(normal, halfVec), 0.0);
+ return pow(ndoth, shininess);
+}
+
+VertexOutput TransformVertex(VertexInput input)
+{
+ VertexOutput output;
+ output.clipPos = mul(ViewProjection, float4(input.position, 1.0));
+ output.worldNormal = input.normal;
+ output.uv = input.uv;
+ return output;
+}
+
+#endif // LIGHTING_HLSL
diff --git a/test/resources/repos/hlsl/test_repo/terrain/terrain_sdf.hlsl b/test/resources/repos/hlsl/test_repo/terrain/terrain_sdf.hlsl
new file mode 100644
index 00000000..a748a270
--- /dev/null
+++ b/test/resources/repos/hlsl/test_repo/terrain/terrain_sdf.hlsl
@@ -0,0 +1,33 @@
+#ifndef TERRAIN_SDF_HLSL
+#define TERRAIN_SDF_HLSL
+
+#include "../common.hlsl"
+
+struct SDFBrickData
+{
+ float3 center;
+ float halfExtent;
+ int resolution;
+ float maxDistance;
+};
+
+float3 WorldOffset;
+
+float SampleSDF(float3 worldPos, SDFBrickData brick)
+{
+ float3 localPos = worldPos - brick.center;
+ float dist = length(localPos) - brick.halfExtent;
+ return dist;
+}
+
+float3 CalculateGradient(float3 worldPos, SDFBrickData brick)
+{
+ float eps = 0.01;
+ float3 gradient;
+ gradient.x = SampleSDF(worldPos + float3(eps, 0, 0), brick) - SampleSDF(worldPos - float3(eps, 0, 0), brick);
+ gradient.y = SampleSDF(worldPos + float3(0, eps, 0), brick) - SampleSDF(worldPos - float3(0, eps, 0), brick);
+ gradient.z = SampleSDF(worldPos + float3(0, 0, eps), brick) - SampleSDF(worldPos - float3(0, 0, eps), brick);
+ return SafeNormalize(gradient);
+}
+
+#endif // TERRAIN_SDF_HLSL
diff --git a/test/resources/repos/lean4/test_repo/Helper.lean b/test/resources/repos/lean4/test_repo/Helper.lean
new file mode 100644
index 00000000..6112bfcd
--- /dev/null
+++ b/test/resources/repos/lean4/test_repo/Helper.lean
@@ -0,0 +1,16 @@
+structure Calculator where
+ name : String
+ version : Nat
+ deriving Repr
+
+def add (x y : Nat) : Nat :=
+ x + y
+
+def subtract (x y : Nat) : Int :=
+ Int.ofNat x - Int.ofNat y
+
+def isPositive (x : Int) : Bool :=
+ x > 0
+
+def absolute (x : Int) : Int :=
+ if isPositive x then x else -x
diff --git a/test/resources/repos/lean4/test_repo/Main.lean b/test/resources/repos/lean4/test_repo/Main.lean
new file mode 100644
index 00000000..261ef8ff
--- /dev/null
+++ b/test/resources/repos/lean4/test_repo/Main.lean
@@ -0,0 +1,24 @@
+import Helper
+
+def multiply (x y : Nat) : Nat :=
+ x * y
+
+def calculate (c : Calculator) (op : String) (x y : Nat) : Option Int :=
+ match op with
+ | "add" => some (Int.ofNat (add x y))
+ | "subtract" => some (subtract x y)
+ | "multiply" => some (Int.ofNat (multiply x y))
+ | _ => none
+
+def main : IO Unit := do
+ let c : Calculator := { name := "TestCalc", version := 1 }
+ IO.println s!"Using {c.name} version {c.version}"
+ let result1 := add 5 3
+ IO.println s!"5 + 3 = {result1}"
+ let result2 := subtract 10 4
+ IO.println s!"10 - 4 = {result2}"
+ match calculate c "multiply" 6 7 with
+ | some result => IO.println s!"6 * 7 = {result}"
+ | none => IO.println "Calculation failed"
+ IO.println s!"Is 5 positive? {isPositive 5}"
+ IO.println s!"Absolute of -10: {absolute (-10)}"
diff --git a/test/resources/repos/lean4/test_repo/lake-manifest.json b/test/resources/repos/lean4/test_repo/lake-manifest.json
new file mode 100644
index 00000000..5a610bd8
--- /dev/null
+++ b/test/resources/repos/lean4/test_repo/lake-manifest.json
@@ -0,0 +1,5 @@
+{"version": "1.1.0",
+ "packagesDir": ".lake/packages",
+ "packages": [],
+ "name": "test_repo",
+ "lakeDir": ".lake"}
diff --git a/test/resources/repos/lean4/test_repo/lakefile.lean b/test/resources/repos/lean4/test_repo/lakefile.lean
new file mode 100644
index 00000000..cb410f19
--- /dev/null
+++ b/test/resources/repos/lean4/test_repo/lakefile.lean
@@ -0,0 +1,9 @@
+import Lake
+open Lake DSL
+
+package «test_repo» where
+ leanOptions := #[⟨`autoImplicit, false⟩]
+
+@[default_target]
+lean_lib «Main» where
+ roots := #[`Main, `Helper]
diff --git a/test/resources/repos/lean4/test_repo/lean-toolchain b/test/resources/repos/lean4/test_repo/lean-toolchain
new file mode 100644
index 00000000..7638861c
--- /dev/null
+++ b/test/resources/repos/lean4/test_repo/lean-toolchain
@@ -0,0 +1 @@
+leanprover/lean4:stable
diff --git a/test/resources/repos/luau/test_repo/.luaurc b/test/resources/repos/luau/test_repo/.luaurc
new file mode 100644
index 00000000..70eed5e8
--- /dev/null
+++ b/test/resources/repos/luau/test_repo/.luaurc
@@ -0,0 +1,9 @@
+{
+ "languageMode": "strict",
+ "lint": {
+ "*": true
+ },
+ "aliases": {
+ "Packages": "Packages/"
+ }
+}
\ No newline at end of file
diff --git a/test/resources/repos/luau/test_repo/src/init.luau b/test/resources/repos/luau/test_repo/src/init.luau
new file mode 100644
index 00000000..e8085945
--- /dev/null
+++ b/test/resources/repos/luau/test_repo/src/init.luau
@@ -0,0 +1,26 @@
+local module = require("./module")
+
+export type Config = {
+ name: string,
+ value: number,
+ enabled: boolean,
+}
+
+local function createConfig(name: string, value: number): Config
+ return {
+ name = name,
+ value = value,
+ enabled = true,
+ }
+end
+
+local function main()
+ local config = createConfig("test", 42)
+ local result = module.process(config)
+ print(result)
+end
+
+return {
+ createConfig = createConfig,
+ main = main,
+}
\ No newline at end of file
diff --git a/test/resources/repos/luau/test_repo/src/module.luau b/test/resources/repos/luau/test_repo/src/module.luau
new file mode 100644
index 00000000..09170f3d
--- /dev/null
+++ b/test/resources/repos/luau/test_repo/src/module.luau
@@ -0,0 +1,12 @@
+local function process(data: { name: string, value: number }): string
+ return `Processing {data.name} with value {data.value}`
+end
+
+local function helper(x: number, y: number): number
+ return x + y
+end
+
+return {
+ process = process,
+ helper = helper,
+}
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/bin/dune b/test/resources/repos/ocaml/test_repo/bin/dune
new file mode 100644
index 00000000..67e87ae8
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/bin/dune
@@ -0,0 +1,4 @@
+(executable
+ (public_name test_repo)
+ (name main)
+ (libraries test_repo))
diff --git a/test/resources/repos/ocaml/test_repo/bin/main.ml b/test/resources/repos/ocaml/test_repo/bin/main.ml
new file mode 100644
index 00000000..3efa44fe
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/bin/main.ml
@@ -0,0 +1,9 @@
+open Test_repo
+
+let n = 20
+
+let () =
+ let res = fib n in
+ Printf.printf "fib(%d) = %d\n" n res;
+ let greeting = DemoModule.someFunction "Hello" in
+ Printf.printf "%s\n" greeting
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/dune-project b/test/resources/repos/ocaml/test_repo/dune-project
new file mode 100644
index 00000000..0bb5a43d
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/dune-project
@@ -0,0 +1,26 @@
+(lang dune 3.18)
+
+(name test_repo)
+
+(generate_opam_files true)
+
+(source
+ (github username/reponame))
+
+(authors "Author Name ")
+
+(maintainers "Maintainer Name ")
+
+(license LICENSE)
+
+(documentation https://url/to/documentation)
+
+(package
+ (name test_repo)
+ (synopsis "A short synopsis")
+ (description "A longer description")
+ (depends ocaml)
+ (tags
+ ("add topics" "to describe" your project)))
+
+; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
diff --git a/test/resources/repos/ocaml/test_repo/lib/dune b/test/resources/repos/ocaml/test_repo/lib/dune
new file mode 100644
index 00000000..fe41978f
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/lib/dune
@@ -0,0 +1,3 @@
+(library
+ (public_name test_repo)
+ (name test_repo))
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/lib/test_repo.ml b/test/resources/repos/ocaml/test_repo/lib/test_repo.ml
new file mode 100644
index 00000000..8befffa2
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/lib/test_repo.ml
@@ -0,0 +1,12 @@
+module DemoModule = struct
+ type value = string
+
+ let someFunction s =
+ s ^ " More String"
+end
+
+let rec fib n =
+ if n < 2 then 1
+ else fib (n-1) + fib (n-2)
+
+let num_domains = 2
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/lib/test_repo.mli b/test/resources/repos/ocaml/test_repo/lib/test_repo.mli
new file mode 100644
index 00000000..b6b33418
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/lib/test_repo.mli
@@ -0,0 +1,7 @@
+module DemoModule : sig
+ type value = string
+ val someFunction : string -> string
+end
+
+val fib : int -> int
+val num_domains : int
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/test/dune b/test/resources/repos/ocaml/test_repo/test/dune
new file mode 100644
index 00000000..f4359ea4
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/test/dune
@@ -0,0 +1,3 @@
+(test
+ (name test_test_repo)
+ (libraries test_repo))
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/test/test_test_repo.ml b/test/resources/repos/ocaml/test_repo/test/test_test_repo.ml
new file mode 100644
index 00000000..3efbaae3
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/test/test_test_repo.ml
@@ -0,0 +1,18 @@
+open Test_repo
+
+let test_fib () =
+ assert (fib 0 = 1);
+ assert (fib 1 = 1);
+ assert (fib 2 = 2);
+ assert (fib 5 = 8);
+ Printf.printf "fib tests passed\n"
+
+let test_demo_module () =
+ let result = DemoModule.someFunction "Test" in
+ assert (result = "Test More String");
+ Printf.printf "DemoModule tests passed\n"
+
+let () =
+ test_fib ();
+ test_demo_module ();
+ Printf.printf "All tests passed!\n"
\ No newline at end of file
diff --git a/test/resources/repos/ocaml/test_repo/test_repo.opam b/test/resources/repos/ocaml/test_repo/test_repo.opam
new file mode 100644
index 00000000..ab7cba25
--- /dev/null
+++ b/test/resources/repos/ocaml/test_repo/test_repo.opam
@@ -0,0 +1,32 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "A short synopsis"
+description: "A longer description"
+maintainer: ["Maintainer Name "]
+authors: ["Author Name "]
+license: "LICENSE"
+tags: ["add topics" "to describe" "your" "project"]
+homepage: "https://github.com/username/reponame"
+doc: "https://url/to/documentation"
+bug-reports: "https://github.com/username/reponame/issues"
+depends: [
+ "dune" {>= "3.18"}
+ "ocaml"
+ "odoc" {with-doc}
+]
+build: [
+ ["dune" "subst"] {dev}
+ [
+ "dune"
+ "build"
+ "-p"
+ name
+ "-j"
+ jobs
+ "@install"
+ "@runtest" {with-test}
+ "@doc" {with-doc}
+ ]
+]
+dev-repo: "git+https://github.com/username/reponame.git"
+x-maintenance-intent: ["(latest)"]
diff --git a/test/resources/repos/php/test_repo/sample.php b/test/resources/repos/php/test_repo/sample.php
new file mode 100644
index 00000000..08e60bc3
--- /dev/null
+++ b/test/resources/repos/php/test_repo/sample.php
@@ -0,0 +1,122 @@
+name = $name;
+ $this->age = $age;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getAge(): int
+ {
+ return $this->age;
+ }
+
+ abstract public function describe(): string;
+}
+
+/**
+ * A concrete animal that can greet visitors.
+ */
+class Dog extends Animal implements GreeterInterface
+{
+ private string $breed;
+
+ public function __construct(string $name, int $age, string $breed)
+ {
+ parent::__construct($name, $age);
+ $this->breed = $breed;
+ }
+
+ public function greet(string $visitorName): string
+ {
+ return "Woof! I'm {$this->name}. Hello, {$visitorName}!";
+ }
+
+ public function getBreed(): string
+ {
+ return $this->breed;
+ }
+
+ public function describe(): string
+ {
+ return "Dog: {$this->name} ({$this->breed}), age {$this->age}";
+ }
+
+ public function fetch(string $item): string
+ {
+ return "{$this->name} fetches the {$item}!";
+ }
+}
+
+/**
+ * Another concrete animal.
+ */
+class Cat extends Animal
+{
+ private bool $indoor;
+
+ public function __construct(string $name, int $age, bool $indoor = true)
+ {
+ parent::__construct($name, $age);
+ $this->indoor = $indoor;
+ }
+
+ public function isIndoor(): bool
+ {
+ return $this->indoor;
+ }
+
+ public function describe(): string
+ {
+ $type = $this->indoor ? 'indoor' : 'outdoor';
+ return "Cat: {$this->name} ({$type}), age {$this->age}";
+ }
+}
+
+const MAX_ANIMALS = 100;
+const DEFAULT_BREED = 'Mixed';
+
+/**
+ * Factory function to create an animal by type name.
+ */
+function createAnimal(string $type, string $name, int $age): Animal
+{
+ return match ($type) {
+ 'dog' => new Dog($name, $age, DEFAULT_BREED),
+ 'cat' => new Cat($name, $age),
+ default => throw new \InvalidArgumentException("Unknown animal type: {$type}"),
+ };
+}
+
+/**
+ * Returns a summary string for a list of animals.
+ *
+ * @param Animal[] $animals
+ */
+function summarizeAnimals(array $animals): string
+{
+ return implode(', ', array_map(fn(Animal $a) => $a->describe(), $animals));
+}
diff --git a/test/resources/repos/solidity/test_repo/.gitignore b/test/resources/repos/solidity/test_repo/.gitignore
new file mode 100644
index 00000000..f9a2eef3
--- /dev/null
+++ b/test/resources/repos/solidity/test_repo/.gitignore
@@ -0,0 +1,4 @@
+out/
+cache/
+artifacts/
+node_modules/
diff --git a/test/resources/repos/solidity/test_repo/contracts/Token.sol b/test/resources/repos/solidity/test_repo/contracts/Token.sol
new file mode 100644
index 00000000..92c698c6
--- /dev/null
+++ b/test/resources/repos/solidity/test_repo/contracts/Token.sol
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./interfaces/IERC20.sol";
+import "./lib/SafeMath.sol";
+
+/// @title Token
+/// @notice A simple ERC-20 token implementation used as a test fixture.
+contract Token is IERC20 {
+ using SafeMath for uint256;
+
+ // -------------------------------------------------------------------------
+ // State variables
+ // -------------------------------------------------------------------------
+
+ string public name;
+ string public symbol;
+ uint8 public decimals;
+ uint256 private _totalSupply;
+
+ mapping(address => uint256) private _balances;
+ mapping(address => mapping(address => uint256)) private _allowances;
+
+ // -------------------------------------------------------------------------
+ // Errors
+ // -------------------------------------------------------------------------
+
+ /// @notice Thrown when transferring to the zero address.
+ error ZeroAddress();
+
+ /// @notice Thrown when the caller's balance is insufficient.
+ error InsufficientBalance(address account, uint256 required, uint256 available);
+
+ /// @notice Thrown when the allowance is insufficient.
+ error InsufficientAllowance(address spender, uint256 required, uint256 available);
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ /// @param _name Human-readable token name.
+ /// @param _symbol Token ticker symbol.
+ /// @param supply Initial supply minted to `msg.sender` (in whole tokens).
+ constructor(string memory _name, string memory _symbol, uint256 supply) {
+ name = _name;
+ symbol = _symbol;
+ decimals = 18;
+ _mint(msg.sender, supply * 10 ** decimals);
+ }
+
+ // -------------------------------------------------------------------------
+ // IERC20 view functions
+ // -------------------------------------------------------------------------
+
+ /// @inheritdoc IERC20
+ function totalSupply() external view override returns (uint256) {
+ return _totalSupply;
+ }
+
+ /// @inheritdoc IERC20
+ function balanceOf(address account) external view override returns (uint256) {
+ return _balances[account];
+ }
+
+ /// @inheritdoc IERC20
+ function allowance(address owner, address spender) external view override returns (uint256) {
+ return _allowances[owner][spender];
+ }
+
+ // -------------------------------------------------------------------------
+ // IERC20 mutating functions
+ // -------------------------------------------------------------------------
+
+ /// @inheritdoc IERC20
+ function transfer(address to, uint256 amount) external override returns (bool) {
+ _transfer(msg.sender, to, amount);
+ return true;
+ }
+
+ /// @inheritdoc IERC20
+ function approve(address spender, uint256 amount) external override returns (bool) {
+ _approve(msg.sender, spender, amount);
+ return true;
+ }
+
+ /// @inheritdoc IERC20
+ function transferFrom(address from, address to, uint256 amount) external override returns (bool) {
+ uint256 currentAllowance = _allowances[from][msg.sender];
+ if (currentAllowance < amount) {
+ revert InsufficientAllowance(msg.sender, amount, currentAllowance);
+ }
+ _approve(from, msg.sender, currentAllowance.sub(amount));
+ _transfer(from, to, amount);
+ return true;
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal helpers
+ // -------------------------------------------------------------------------
+
+ function _transfer(address from, address to, uint256 amount) internal {
+ if (to == address(0)) revert ZeroAddress();
+ uint256 fromBalance = _balances[from];
+ if (fromBalance < amount) {
+ revert InsufficientBalance(from, amount, fromBalance);
+ }
+ _balances[from] = fromBalance.sub(amount);
+ _balances[to] = _balances[to].add(amount);
+ emit Transfer(from, to, amount);
+ }
+
+ function _approve(address owner, address spender, uint256 amount) internal {
+ if (owner == address(0) || spender == address(0)) revert ZeroAddress();
+ _allowances[owner][spender] = amount;
+ emit Approval(owner, spender, amount);
+ }
+
+ function _mint(address account, uint256 amount) internal {
+ if (account == address(0)) revert ZeroAddress();
+ _totalSupply = _totalSupply.add(amount);
+ _balances[account] = _balances[account].add(amount);
+ emit Transfer(address(0), account, amount);
+ }
+}
diff --git a/test/resources/repos/solidity/test_repo/contracts/interfaces/IERC20.sol b/test/resources/repos/solidity/test_repo/contracts/interfaces/IERC20.sol
new file mode 100644
index 00000000..48863b01
--- /dev/null
+++ b/test/resources/repos/solidity/test_repo/contracts/interfaces/IERC20.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+/// @title IERC20
+/// @notice Minimal ERC-20 interface used by the test token.
+interface IERC20 {
+ /// @notice Emitted when tokens are transferred between accounts.
+ event Transfer(address indexed from, address indexed to, uint256 value);
+
+ /// @notice Emitted when an allowance is set via `approve`.
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+
+ /// @notice Returns the total token supply.
+ function totalSupply() external view returns (uint256);
+
+ /// @notice Returns the token balance of `account`.
+ function balanceOf(address account) external view returns (uint256);
+
+ /// @notice Transfers `amount` tokens to `to`.
+ function transfer(address to, uint256 amount) external returns (bool);
+
+ /// @notice Returns the remaining allowance that `spender` has over `owner`'s tokens.
+ function allowance(address owner, address spender) external view returns (uint256);
+
+ /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
+ function approve(address spender, uint256 amount) external returns (bool);
+
+ /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
+ function transferFrom(address from, address to, uint256 amount) external returns (bool);
+}
diff --git a/test/resources/repos/solidity/test_repo/contracts/lib/SafeMath.sol b/test/resources/repos/solidity/test_repo/contracts/lib/SafeMath.sol
new file mode 100644
index 00000000..e8e9de1e
--- /dev/null
+++ b/test/resources/repos/solidity/test_repo/contracts/lib/SafeMath.sol
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+/// @title SafeMath
+/// @notice Arithmetic helpers with overflow checks (illustrative — 0.8+ reverts natively).
+library SafeMath {
+ /// @notice Returns the sum of `a` and `b`, reverting on overflow.
+ function add(uint256 a, uint256 b) internal pure returns (uint256) {
+ return a + b;
+ }
+
+ /// @notice Returns `a` minus `b`, reverting on underflow.
+ function sub(uint256 a, uint256 b) internal pure returns (uint256) {
+ require(b <= a, "SafeMath: subtraction underflow");
+ return a - b;
+ }
+
+ /// @notice Returns the product of `a` and `b`, reverting on overflow.
+ function mul(uint256 a, uint256 b) internal pure returns (uint256) {
+ if (a == 0) return 0;
+ return a * b;
+ }
+
+ /// @notice Returns the integer division of `a` by `b`, reverting on division by zero.
+ function div(uint256 a, uint256 b) internal pure returns (uint256) {
+ require(b > 0, "SafeMath: division by zero");
+ return a / b;
+ }
+}
diff --git a/test/resources/repos/solidity/test_repo/foundry.toml b/test/resources/repos/solidity/test_repo/foundry.toml
new file mode 100644
index 00000000..dc262024
--- /dev/null
+++ b/test/resources/repos/solidity/test_repo/foundry.toml
@@ -0,0 +1,5 @@
+[profile.default]
+src = "contracts"
+out = "out"
+libs = []
+solc = "0.8.20"
diff --git a/test/serena/config/test_global_ignored_paths.py b/test/serena/config/test_global_ignored_paths.py
index 23481373..af33ceea 100644
--- a/test/serena/config/test_global_ignored_paths.py
+++ b/test/serena/config/test_global_ignored_paths.py
@@ -20,9 +20,7 @@ def _create_test_project(
ignored_paths=project_ignored_paths or [],
ignore_all_files_in_gitignore=False,
)
- serena_config: SerenaConfig | None = None
- if global_ignored_paths:
- serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=global_ignored_paths)
+ serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=global_ignored_paths)
return Project(
project_root=str(project_root),
project_config=config,
@@ -84,21 +82,6 @@ class TestGlobalIgnoredPaths:
# Non-ignored files still accessible
assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg" / "index.js"))
- def test_default_global_ignored_paths_backward_compatible(self) -> None:
- """Project created without global_ignored_paths parameter works as before."""
- config = ProjectConfig(
- project_name="test_project",
- languages=[Language.PYTHON],
- ignored_paths=["build"],
- ignore_all_files_in_gitignore=False,
- )
- project = Project(
- project_root=str(self.project_path),
- project_config=config,
- )
- assert project.is_ignored_path(str(self.project_path / "build" / "output.js"))
- assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg" / "index.js"))
-
def test_duplicate_patterns_across_global_and_project(self) -> None:
"""Duplicate patterns across global and project do not cause errors."""
project = _create_test_project(
@@ -161,7 +144,8 @@ class TestRegisteredProjectGlobalIgnoredPaths:
project_root=str(self.project_path),
project_config=config,
)
- project = registered.get_project_instance(serena_config=None)
+ serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=[])
+ project = registered.get_project_instance(serena_config=serena_config)
assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js"))
def test_from_project_root_passes_global_ignored_paths(self) -> None:
@@ -175,6 +159,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"])
registered = RegisteredProject.from_project_root(
str(self.project_path),
+ serena_config=serena_config,
)
project = registered.get_project_instance(serena_config=serena_config)
assert project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js"))
diff --git a/test/serena/config/test_serena_config.py b/test/serena/config/test_serena_config.py
index e4515a0d..8a2eedbb 100644
--- a/test/serena/config/test_serena_config.py
+++ b/test/serena/config/test_serena_config.py
@@ -1,4 +1,5 @@
import logging
+import os
import shutil
import tempfile
from pathlib import Path
@@ -6,10 +7,18 @@ from pathlib import Path
import pytest
from serena.agent import SerenaAgent
-from serena.config.serena_config import LanguageBackend, ProjectConfig, RegisteredProject, SerenaConfig
-from serena.constants import PROJECT_TEMPLATE_FILE
-from serena.project import Project
+from serena.config.serena_config import (
+ DEFAULT_PROJECT_SERENA_FOLDER_LOCATION,
+ LanguageBackend,
+ ProjectConfig,
+ RegisteredProject,
+ SerenaConfig,
+ SerenaConfigError,
+)
+from serena.constants import PROJECT_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME
+from serena.project import MemoriesManager, Project
from solidlsp.ls_config import Language
+from test.conftest import create_default_serena_config
class TestProjectConfigAutogenerate:
@@ -19,6 +28,7 @@ class TestProjectConfigAutogenerate:
"""Set up test environment before each test method."""
# Create a temporary directory for testing
self.test_dir = tempfile.mkdtemp()
+ self.serena_config = create_default_serena_config()
self.project_path = Path(self.test_dir)
def teardown_method(self):
@@ -27,12 +37,18 @@ class TestProjectConfigAutogenerate:
shutil.rmtree(self.test_dir)
def test_autogenerate_empty_directory(self):
- """Test that autogenerate raises ValueError with helpful message for empty directory."""
- with pytest.raises(ValueError) as exc_info:
- ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
+ """Test that autogenerate succeeds with empty languages list for an empty directory."""
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
- error_message = str(exc_info.value)
- assert "No source files found" in error_message
+ assert config.project_name == self.project_path.name
+ assert config.languages == []
+
+ def test_autogenerate_empty_directory_logs_warning(self, caplog):
+ """Test that autogenerate logs a warning when no language files are found."""
+ with caplog.at_level(logging.WARNING):
+ ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
+
+ assert any("No source files for supported language servers were found" in msg for msg in caplog.messages)
def test_autogenerate_with_python_files(self):
"""Test successful autogeneration with Python source files."""
@@ -41,7 +57,7 @@ class TestProjectConfigAutogenerate:
python_file.write_text("def hello():\n print('Hello, world!')\n")
# Run autogenerate
- config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
# Verify the configuration
assert config.project_name == self.project_path.name
@@ -53,7 +69,7 @@ class TestProjectConfigAutogenerate:
(self.project_path / "small.js").write_text("console.log('JS');")
# Run autogenerate - should pick Python as dominant
- config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
assert config.languages == [Language.TYPESCRIPT]
@@ -65,7 +81,7 @@ class TestProjectConfigAutogenerate:
(self.project_path / "small.js").write_text("console.log('JS');")
# Run autogenerate - should pick Python as dominant
- config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
assert config.languages == [Language.PYTHON]
@@ -76,7 +92,7 @@ class TestProjectConfigAutogenerate:
go_file.write_text("package main\n\nfunc main() {}\n")
# Run autogenerate with save_to_disk=True
- config = ProjectConfig.autogenerate(self.project_path, save_to_disk=True)
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=True)
# Verify the configuration file was created
config_path = self.project_path / ".serena" / "project.yml"
@@ -90,12 +106,12 @@ class TestProjectConfigAutogenerate:
non_existent = self.project_path / "does_not_exist"
with pytest.raises(FileNotFoundError) as exc_info:
- ProjectConfig.autogenerate(non_existent, save_to_disk=False)
+ ProjectConfig.autogenerate(non_existent, self.serena_config, save_to_disk=False)
assert "Project root not found" in str(exc_info.value)
def test_autogenerate_with_gitignored_files_only(self):
- """Test autogenerate behavior when only gitignored files exist."""
+ """Test autogenerate creates a project with empty languages when only gitignored files exist."""
# Create a .gitignore that ignores all Python files
gitignore = self.project_path / ".gitignore"
gitignore.write_text("*.py\n")
@@ -103,11 +119,11 @@ class TestProjectConfigAutogenerate:
# Create Python files that will be ignored
(self.project_path / "ignored.py").write_text("print('ignored')")
- # Should still raise ValueError as no source files are detected
- with pytest.raises(ValueError) as exc_info:
- ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
+ # Should succeed with empty languages (gitignored files are not counted)
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
- assert "No source files found" in str(exc_info.value)
+ assert config.project_name == self.project_path.name
+ assert config.languages == []
def test_autogenerate_custom_project_name(self):
"""Test autogenerate with custom project name."""
@@ -117,7 +133,7 @@ class TestProjectConfigAutogenerate:
# Run autogenerate with custom name
custom_name = "my-custom-project"
- config = ProjectConfig.autogenerate(self.project_path, project_name=custom_name, save_to_disk=False)
+ config = ProjectConfig.autogenerate(self.project_path, self.serena_config, project_name=custom_name, save_to_disk=False)
assert config.project_name == custom_name
assert config.languages == [Language.TYPESCRIPT]
@@ -125,7 +141,7 @@ class TestProjectConfigAutogenerate:
class TestProjectConfig:
def test_template_is_complete(self):
- _, is_complete = ProjectConfig._load_yaml(PROJECT_TEMPLATE_FILE)
+ _, is_complete = ProjectConfig._load_yaml_dict(PROJECT_TEMPLATE_FILE)
assert is_complete, "Project template YAML is incomplete; all fields must be present (with descriptions)."
@@ -167,21 +183,21 @@ class TestProjectConfigLanguageBackend:
def test_language_backend_parsed_from_dict(self):
"""Test that _from_dict parses language_backend correctly."""
template_path = PROJECT_TEMPLATE_FILE
- data, _ = ProjectConfig._load_yaml(template_path)
+ data, _ = ProjectConfig._load_yaml_dict(template_path)
data["project_name"] = "test"
data["languages"] = ["python"]
data["language_backend"] = "JetBrains"
- config = ProjectConfig._from_dict(data)
+ config = ProjectConfig._from_dict(data, local_override_keys=[])
assert config.language_backend == LanguageBackend.JETBRAINS
def test_language_backend_none_when_missing_from_dict(self):
"""Test that _from_dict handles missing language_backend gracefully."""
template_path = PROJECT_TEMPLATE_FILE
- data, _ = ProjectConfig._load_yaml(template_path)
+ data, _ = ProjectConfig._load_yaml_dict(template_path)
data["project_name"] = "test"
data["languages"] = ["python"]
data.pop("language_backend", None)
- config = ProjectConfig._from_dict(data)
+ config = ProjectConfig._from_dict(data, local_override_keys=[])
assert config.language_backend is None
@@ -191,6 +207,12 @@ def _make_config_with_project(
global_backend: LanguageBackend = LanguageBackend.LSP,
) -> tuple[SerenaConfig, str]:
"""Create a SerenaConfig with a single registered project and return (config, project_name)."""
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ log_level=logging.ERROR,
+ language_backend=global_backend,
+ )
project = Project(
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
project_config=ProjectConfig(
@@ -198,12 +220,7 @@ def _make_config_with_project(
languages=[Language.PYTHON],
language_backend=language_backend,
),
- )
- config = SerenaConfig(
- gui_log_window=False,
- web_dashboard=False,
- log_level=logging.ERROR,
- language_backend=global_backend,
+ serena_config=config,
)
config.projects = [RegisteredProject.from_project_instance(project)]
return config, project_name
@@ -217,8 +234,7 @@ class TestEffectiveLanguageBackend:
config, name = _make_config_with_project("test_proj", language_backend=None, global_backend=LanguageBackend.LSP)
agent = SerenaAgent(project=name, serena_config=config)
try:
- assert agent.get_language_backend() == LanguageBackend.LSP
- assert agent.is_using_language_server() is True
+ assert agent.get_language_backend().is_lsp()
finally:
agent.shutdown(timeout=5)
@@ -229,8 +245,7 @@ class TestEffectiveLanguageBackend:
)
agent = SerenaAgent(project=name, serena_config=config)
try:
- assert agent.get_language_backend() == LanguageBackend.JETBRAINS
- assert agent.is_using_language_server() is False
+ assert agent.get_language_backend().is_jetbrains()
finally:
agent.shutdown(timeout=5)
@@ -261,6 +276,7 @@ class TestEffectiveLanguageBackend:
languages=[Language.PYTHON],
language_backend=LanguageBackend.JETBRAINS,
),
+ serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(jb_project))
@@ -283,6 +299,7 @@ class TestEffectiveLanguageBackend:
languages=[Language.PYTHON],
language_backend=LanguageBackend.LSP,
),
+ serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(lsp_project2))
@@ -305,6 +322,7 @@ class TestEffectiveLanguageBackend:
languages=[Language.PYTHON],
language_backend=None,
),
+ serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(proj2))
@@ -314,3 +332,197 @@ class TestEffectiveLanguageBackend:
agent.activate_project_from_path_or_name("proj2")
finally:
agent.shutdown(timeout=5)
+
+
+class TestGetConfiguredProjectSerenaFolder:
+ """Tests for SerenaConfig.get_configured_project_serena_folder (pure template resolution)."""
+
+ def test_default_location(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ )
+ result = config.get_configured_project_serena_folder("/home/user/myproject")
+ assert result == os.path.abspath("/home/user/myproject/.serena")
+
+ def test_custom_location_with_project_folder_name(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="/projects-metadata/$projectFolderName/.serena",
+ )
+ result = config.get_configured_project_serena_folder("/home/user/myproject")
+ assert result == os.path.abspath("/projects-metadata/myproject/.serena")
+
+ def test_custom_location_with_project_dir(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="$projectDir/.custom-serena",
+ )
+ result = config.get_configured_project_serena_folder("/home/user/myproject")
+ assert result == os.path.abspath("/home/user/myproject/.custom-serena")
+
+ def test_custom_location_with_both_placeholders(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="/data/$projectFolderName/$projectDir/.serena",
+ )
+ result = config.get_configured_project_serena_folder("/home/user/proj")
+ assert result == os.path.abspath("/data/proj/home/user/proj/.serena")
+
+ def test_default_field_value(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ )
+ assert config.project_serena_folder_location == DEFAULT_PROJECT_SERENA_FOLDER_LOCATION
+
+ def test_rejects_unknown_placeholder(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="$projectDir/$unknownVar/.serena",
+ )
+ with pytest.raises(SerenaConfigError, match=r"Unknown placeholder '\$unknownVar'"):
+ config.get_configured_project_serena_folder("/home/user/myproject")
+
+ def test_rejects_typo_projectDirs(self):
+ """$projectDirs should not be silently treated as $projectDir + 's'."""
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="$projectDirs/.serena",
+ )
+ with pytest.raises(SerenaConfigError, match=r"Unknown placeholder '\$projectDirs'"):
+ config.get_configured_project_serena_folder("/home/user/myproject")
+
+ def test_rejects_typo_projectfoldername_lowercase(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="/data/$projectfoldername/.serena",
+ )
+ with pytest.raises(SerenaConfigError, match=r"Unknown placeholder '\$projectfoldername'"):
+ config.get_configured_project_serena_folder("/home/user/myproject")
+
+ def test_no_placeholders_is_valid(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="/fixed/path/.serena",
+ )
+ result = config.get_configured_project_serena_folder("/home/user/myproject")
+ assert result == os.path.abspath("/fixed/path/.serena")
+
+ def test_error_message_lists_supported_placeholders(self):
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="$bogus/.serena",
+ )
+ with pytest.raises(SerenaConfigError, match=r"\$projectDir.*\$projectFolderName|\$projectFolderName.*\$projectDir"):
+ config.get_configured_project_serena_folder("/home/user/myproject")
+
+
+class TestProjectSerenaDataFolder:
+ """Tests for SerenaConfig.get_project_serena_folder fallback logic (via Project)."""
+
+ def setup_method(self):
+ self.test_dir = tempfile.mkdtemp()
+ self.project_path = Path(self.test_dir) / "myproject"
+ self.project_path.mkdir()
+ (self.project_path / "main.py").write_text("print('hello')\n")
+
+ def teardown_method(self):
+ shutil.rmtree(self.test_dir)
+
+ def _make_project(self, serena_config: "SerenaConfig | None" = None) -> Project:
+ project_config = ProjectConfig(
+ project_name="myproject",
+ languages=[Language.PYTHON],
+ )
+ project = Project(
+ project_root=str(self.project_path),
+ project_config=project_config,
+ serena_config=serena_config,
+ )
+ project._ignore_spec_available.wait()
+ return project
+
+ def test_default_config_creates_in_project_dir(self):
+ config = SerenaConfig(gui_log_window=False, web_dashboard=False)
+ project = self._make_project(config)
+ expected = os.path.abspath(str(self.project_path / SERENA_MANAGED_DIR_NAME))
+ assert project.path_to_serena_data_folder() == expected
+
+ def test_custom_location_creates_outside_project(self):
+ custom_base = Path(self.test_dir) / "metadata"
+ custom_base.mkdir()
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location=str(custom_base) + "/$projectFolderName/.serena",
+ )
+ project = self._make_project(config)
+ expected = os.path.abspath(str(custom_base / "myproject" / ".serena"))
+ assert project.path_to_serena_data_folder() == expected
+
+ def test_fallback_to_existing_project_dir(self):
+ """If config points to a non-existent path but .serena exists in the project root, use the existing one."""
+ existing_serena = self.project_path / SERENA_MANAGED_DIR_NAME
+ existing_serena.mkdir()
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location="/nonexistent/path/$projectFolderName/.serena",
+ )
+ project = self._make_project(config)
+ assert project.path_to_serena_data_folder() == str(existing_serena)
+
+ def test_configured_path_takes_precedence_when_exists(self):
+ """If both config path and project root path exist, use the config path."""
+ existing_serena = self.project_path / SERENA_MANAGED_DIR_NAME
+ existing_serena.mkdir()
+
+ custom_base = Path(self.test_dir) / "metadata"
+ custom_serena = custom_base / "myproject" / ".serena"
+ custom_serena.mkdir(parents=True)
+
+ config = SerenaConfig(
+ gui_log_window=False,
+ web_dashboard=False,
+ project_serena_folder_location=str(custom_base) + "/$projectFolderName/.serena",
+ )
+ project = self._make_project(config)
+ assert project.path_to_serena_data_folder() == str(custom_serena)
+
+
+class TestMemoriesManagerCustomPath:
+ """Tests for MemoriesManager with a custom serena data folder."""
+
+ def setup_method(self):
+ self.test_dir = tempfile.mkdtemp()
+ self.data_folder = Path(self.test_dir) / "custom_serena"
+
+ def teardown_method(self):
+ shutil.rmtree(self.test_dir)
+
+ def test_memories_subdir_is_created(self):
+ assert not self.data_folder.exists()
+ MemoriesManager(str(self.data_folder))
+ assert (self.data_folder / "memories").exists()
+
+ def test_save_and_load_memory(self):
+ manager = MemoriesManager(str(self.data_folder))
+ manager.save_memory("test_topic", "test content", is_tool_context=False)
+ content = manager.load_memory("test_topic")
+ assert content == "test content"
+
+ def test_list_memories(self):
+ manager = MemoriesManager(str(self.data_folder))
+ manager.save_memory("topic_a", "content a", is_tool_context=False)
+ manager.save_memory("topic_b", "content b", is_tool_context=False)
+ memories = manager.list_project_memories()
+ assert sorted(memories.get_full_list()) == ["topic_a", "topic_b"]
diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py
index 3dbef5d8..0b40ab28 100644
--- a/test/serena/test_serena_agent.py
+++ b/test/serena/test_serena_agent.py
@@ -21,7 +21,8 @@ from test.solidlsp import clojure as clj
@pytest.fixture
def serena_config():
- """Create an in-memory configuration for tests with test repositories pre-registered."""
+ config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
+
# Create test projects for all supported languages
test_projects = []
for language in [
@@ -37,6 +38,7 @@ def serena_config():
Language.FSHARP,
Language.POWERSHELL,
Language.CPP_CCLS,
+ Language.LEAN4,
]:
repo_path = get_repo_path(language)
if repo_path.exists():
@@ -47,16 +49,16 @@ def serena_config():
project_name=project_name,
languages=[language],
ignored_paths=[],
- excluded_tools=set(),
+ excluded_tools=[],
read_only=False,
ignore_all_files_in_gitignore=True,
initial_prompt="",
encoding="utf-8",
),
+ serena_config=config,
)
test_projects.append(RegisteredProject.from_project_instance(project))
- config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
config.projects = test_projects
return config
@@ -117,37 +119,7 @@ class TestSerenaAgent:
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False)
SerenaAgent(project=project, serena_config=serena_config)
- @pytest.mark.parametrize(
- "serena_agent,symbol_name,expected_kind,expected_file",
- [
- pytest.param(Language.PYTHON, "User", "Class", "models.py", marks=pytest.mark.python),
- pytest.param(Language.GO, "Helper", "Function", "main.go", marks=pytest.mark.go),
- pytest.param(Language.JAVA, "Model", "Class", "Model.java", marks=pytest.mark.java),
- pytest.param(
- Language.KOTLIN,
- "Model",
- "Struct",
- "Model.kt",
- marks=[pytest.mark.kotlin] + ([pytest.mark.skip(reason="Kotlin LSP JVM crashes on restart in CI")] if is_ci else []),
- ),
- pytest.param(Language.RUST, "add", "Function", "lib.rs", marks=pytest.mark.rust),
- pytest.param(Language.TYPESCRIPT, "DemoClass", "Class", "index.ts", marks=pytest.mark.typescript),
- pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
- pytest.param(Language.CLOJURE, "greet", "Function", clj.CORE_PATH, marks=pytest.mark.clojure),
- pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
- pytest.param(Language.FSHARP, "Calculator", "Module", "Calculator.fs", marks=pytest.mark.fsharp),
- pytest.param(Language.POWERSHELL, "function Greet-User ()", "Function", "main.ps1", marks=pytest.mark.powershell),
- pytest.param(Language.CPP_CCLS, "add", "Function", "b.cpp", marks=pytest.mark.cpp),
- ],
- indirect=["serena_agent"],
- )
- def test_find_symbol(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str):
- # skip flaky tests in CI
- # TODO: Revisit the flaky tests and re-enable once the LS issues are resolved #1040
- flaky_languages = {Language.FSHARP, Language.RUST}
- if set(serena_agent.get_active_lsp_languages()).intersection(flaky_languages) and is_ci:
- pytest.skip("Test is flaky and thus skipped in CI environment.")
-
+ def _assert_find_symbol(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str) -> None:
agent = serena_agent
find_symbol_tool = agent.get_tool(FindSymbolTool)
result = find_symbol_tool.apply(name_path_pattern=symbol_name, include_info=True)
@@ -167,62 +139,86 @@ class TestSerenaAgent:
continue
symbol_info = s.get("info")
assert symbol_info, f"Expected symbol info to be present for symbol: {s}"
- assert (
- symbol_name in s["info"]
- ), f"[{serena_agent.get_active_lsp_languages()[0]}] Expected symbol info to contain symbol name {symbol_name}. Info: {s['info']}"
+ assert symbol_name in s["info"], (
+ f"[{serena_agent.get_active_lsp_languages()[0]}] Expected symbol info to contain symbol name {symbol_name}. Info: {s['info']}"
+ )
# special additional test for Java, since Eclipse returns hover in a complex format and we want to make sure to get it right
if s["kind"] == SymbolKind.Class.name and serena_agent.get_active_lsp_languages() == [Language.JAVA]:
assert "A simple model class" in symbol_info, f"Java class docstring not found in symbol info: {s}"
+ @pytest.mark.php
+ @pytest.mark.parametrize("serena_agent", [Language.PHP], indirect=True)
+ def test_find_symbol_within_php_file(self, serena_agent: SerenaAgent) -> None:
+ """Verify find_symbol with a PHP file path routes to the PHP language server.
+
+ This validates the fix in symbol.py (LanguageServerSymbolRetriever.find_symbols):
+ when within_relative_path points to a PHP file, the retriever must use
+ get_language_server() rather than iterating all language servers. Without this
+ fix, non-PHP servers reject the PHP file and no symbols are returned.
+ """
+ find_symbol_tool = serena_agent.get_tool(FindSymbolTool)
+ sample_php = "sample.php"
+
+ result = find_symbol_tool.apply(name_path_pattern="Dog/greet", relative_path=sample_php)
+ symbols = json.loads(result)
+
+ assert len(symbols) > 0, (
+ f"Expected to find Dog/greet in {sample_php} but got empty result. "
+ "This may indicate that find_symbol is not routing to the PHP language server for PHP files."
+ )
+ assert any("greet" in s["name_path"] and sample_php in s["relative_path"] for s in symbols), (
+ f"Dog/greet not found in {sample_php}. Symbols: {symbols}"
+ )
+
@pytest.mark.parametrize(
- "serena_agent,symbol_name,def_file,ref_file",
+ "serena_agent,symbol_name,expected_kind,expected_file",
[
- pytest.param(
- Language.PYTHON,
- "User",
- os.path.join("test_repo", "models.py"),
- os.path.join("test_repo", "services.py"),
- marks=pytest.mark.python,
- ),
- pytest.param(Language.GO, "Helper", "main.go", "main.go", marks=pytest.mark.go),
- pytest.param(
- Language.JAVA,
- "Model",
- os.path.join("src", "main", "java", "test_repo", "Model.java"),
- os.path.join("src", "main", "java", "test_repo", "Main.java"),
- marks=pytest.mark.java,
- ),
+ pytest.param(Language.PYTHON, "User", "Class", "models.py", marks=pytest.mark.python),
+ pytest.param(Language.GO, "Helper", "Function", "main.go", marks=pytest.mark.go),
+ pytest.param(Language.JAVA, "Model", "Class", "Model.java", marks=pytest.mark.java),
pytest.param(
Language.KOTLIN,
"Model",
- os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
- os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
+ "Struct",
+ "Model.kt",
marks=[pytest.mark.kotlin] + ([pytest.mark.skip(reason="Kotlin LSP JVM crashes on restart in CI")] if is_ci else []),
),
- pytest.param(Language.RUST, "add", os.path.join("src", "lib.rs"), os.path.join("src", "main.rs"), marks=pytest.mark.rust),
- pytest.param(Language.TYPESCRIPT, "helperFunction", "index.ts", "use_helper.ts", marks=pytest.mark.typescript),
- pytest.param(Language.PHP, "helperFunction", "helper.php", "index.php", marks=pytest.mark.php),
- pytest.param(
- Language.CLOJURE,
- "multiply",
- clj.CORE_PATH,
- clj.UTILS_PATH,
- marks=pytest.mark.clojure,
- ),
- pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
- pytest.param(Language.FSHARP, "add", "Calculator.fs", "Program.fs", marks=pytest.mark.fsharp),
- pytest.param(Language.POWERSHELL, "function Greet-User ()", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
- pytest.param(Language.CPP_CCLS, "add", "b.cpp", "a.cpp", marks=pytest.mark.cpp),
+ pytest.param(Language.TYPESCRIPT, "DemoClass", "Class", "index.ts", marks=pytest.mark.typescript),
+ pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
+ pytest.param(Language.CLOJURE, "greet", "Function", clj.CORE_PATH, marks=pytest.mark.clojure),
+ pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
+ pytest.param(Language.POWERSHELL, "function Greet-User ()", "Function", "main.ps1", marks=pytest.mark.powershell),
+ pytest.param(Language.CPP_CCLS, "add", "Function", "b.cpp", marks=pytest.mark.cpp),
+ pytest.param(Language.LEAN4, "add", "Method", "Helper.lean", marks=pytest.mark.lean4),
],
indirect=["serena_agent"],
)
- def test_find_symbol_references(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:
- # skip flaky tests in CI
- # TODO: Revisit the flaky tests and re-enable once the LS issues are resolved #1040
- flaky_languages = {Language.TYPESCRIPT}
- if set(serena_agent.get_active_lsp_languages()).intersection(flaky_languages) and is_ci:
- pytest.skip("Test is flaky and thus skipped in CI environment.")
+ def test_find_symbol_stable(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str) -> None:
+ self._assert_find_symbol(serena_agent, symbol_name, expected_kind, expected_file)
+ @pytest.mark.parametrize(
+ "serena_agent,symbol_name,expected_kind,expected_file",
+ [
+ pytest.param(Language.FSHARP, "Calculator", "Module", "Calculator.fs", marks=pytest.mark.fsharp),
+ ],
+ indirect=["serena_agent"],
+ )
+ @pytest.mark.xfail(reason="F# language server is unreliable") # See issue #1040
+ def test_find_symbol_fsharp(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str) -> None:
+ self._assert_find_symbol(serena_agent, symbol_name, expected_kind, expected_file)
+
+ @pytest.mark.parametrize(
+ "serena_agent,symbol_name,expected_kind,expected_file",
+ [
+ pytest.param(Language.RUST, "add", "Function", "lib.rs", marks=pytest.mark.rust),
+ ],
+ indirect=["serena_agent"],
+ )
+ @pytest.mark.xfail(reason="Rust language server is unreliable") # See issue #1040
+ def test_find_symbol_rust(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str) -> None:
+ self._assert_find_symbol(serena_agent, symbol_name, expected_kind, expected_file)
+
+ def _assert_find_symbol_references(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:
agent = serena_agent
# Find the symbol location first
@@ -257,6 +253,72 @@ class TestSerenaAgent:
refs = json.loads(result)
assert contains_ref_with_relative_path(refs, ref_file), f"Expected to find reference to {symbol_name} in {ref_file}. refs={refs}"
+ @pytest.mark.parametrize(
+ "serena_agent,symbol_name,def_file,ref_file",
+ [
+ pytest.param(
+ Language.PYTHON,
+ "User",
+ os.path.join("test_repo", "models.py"),
+ os.path.join("test_repo", "services.py"),
+ marks=pytest.mark.python,
+ ),
+ pytest.param(Language.GO, "Helper", "main.go", "main.go", marks=pytest.mark.go),
+ pytest.param(
+ Language.JAVA,
+ "Model",
+ os.path.join("src", "main", "java", "test_repo", "Model.java"),
+ os.path.join("src", "main", "java", "test_repo", "Main.java"),
+ marks=pytest.mark.java,
+ ),
+ pytest.param(
+ Language.KOTLIN,
+ "Model",
+ os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
+ os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
+ marks=[pytest.mark.kotlin] + ([pytest.mark.skip(reason="Kotlin LSP JVM crashes on restart in CI")] if is_ci else []),
+ ),
+ pytest.param(Language.RUST, "add", os.path.join("src", "lib.rs"), os.path.join("src", "main.rs"), marks=pytest.mark.rust),
+ pytest.param(Language.PHP, "helperFunction", "helper.php", "index.php", marks=pytest.mark.php),
+ pytest.param(
+ Language.CLOJURE,
+ "multiply",
+ clj.CORE_PATH,
+ clj.UTILS_PATH,
+ marks=pytest.mark.clojure,
+ ),
+ pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
+ pytest.param(Language.POWERSHELL, "function Greet-User ()", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
+ pytest.param(Language.CPP_CCLS, "add", "b.cpp", "a.cpp", marks=pytest.mark.cpp),
+ pytest.param(Language.LEAN4, "add", "Helper.lean", "Main.lean", marks=pytest.mark.lean4),
+ ],
+ indirect=["serena_agent"],
+ )
+ def test_find_symbol_references_stable(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:
+ self._assert_find_symbol_references(serena_agent, symbol_name, def_file, ref_file)
+
+ @pytest.mark.parametrize(
+ "serena_agent,symbol_name,def_file,ref_file",
+ [
+ pytest.param(Language.TYPESCRIPT, "helperFunction", "index.ts", "use_helper.ts", marks=pytest.mark.typescript),
+ ],
+ indirect=["serena_agent"],
+ )
+ @pytest.mark.xfail(False, reason="TypeScript language server is unreliable") # NOTE: Testing; may be resolved by #1120; See issue #1040
+ def test_find_symbol_references_typescript(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:
+ self._assert_find_symbol_references(serena_agent, symbol_name, def_file, ref_file)
+
+ @pytest.mark.parametrize(
+ "serena_agent,symbol_name,def_file,ref_file",
+ [
+ pytest.param(Language.FSHARP, "add", "Calculator.fs", "Program.fs", marks=pytest.mark.fsharp),
+ ],
+ indirect=["serena_agent"],
+ )
+ @pytest.mark.xfail(reason="F# language server is unreliable") # See issue #1040
+ def test_find_symbol_references_fsharp(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:
+ self._assert_find_symbol_references(serena_agent, symbol_name, def_file, ref_file)
+
@pytest.mark.parametrize(
"serena_agent,name_path,substring_matching,expected_symbol_name,expected_kind,expected_file",
[
@@ -416,9 +478,9 @@ class TestSerenaAgent:
)
symbols = json.loads(result)
- assert (
- len(symbols) == num_expected
- ), f"Expected to find {num_expected} symbols for overloaded function {name_path}. Symbols found: {symbols}"
+ assert len(symbols) == num_expected, (
+ f"Expected to find {num_expected} symbols for overloaded function {name_path}. Symbols found: {symbols}"
+ )
@pytest.mark.parametrize(
"serena_agent,name_path,relative_path",
diff --git a/test/serena/test_symbol.py b/test/serena/test_symbol.py
index f973d633..60b936da 100644
--- a/test/serena/test_symbol.py
+++ b/test/serena/test_symbol.py
@@ -3,8 +3,8 @@ from unittest.mock import MagicMock
import pytest
from serena.jetbrains.jetbrains_types import SymbolDTO, SymbolDTOKey
+from serena.project import Project
from serena.symbol import LanguageServerSymbol, LanguageServerSymbolRetriever, NamePathComponent, NamePathMatcher
-from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
@@ -235,9 +235,9 @@ class TestSymbolNameMatching:
@pytest.mark.python
class TestLanguageServerSymbolRetriever:
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_request_info(self, language_server: SolidLanguageServer):
- symbol_retriever = LanguageServerSymbolRetriever(language_server)
+ @pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
+ def test_request_info(self, project_with_ls: Project):
+ symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
create_user_method_symbol = symbol_retriever.find("UserService/create_user", within_relative_path="test_repo/services.py")[0]
create_user_method_symbol_info = symbol_retriever.request_info_for_symbol(create_user_method_symbol)
assert "Create a new user and store it" in create_user_method_symbol_info
@@ -280,15 +280,14 @@ def _make_mock_symbols(count: int, *, relative_path: str = "test_repo/services.p
class TestHoverBudget:
"""Tests for symbol_info_budget time budget behavior."""
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_budget_not_exceeded_all_lookups_performed(self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch):
+ @pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
+ def test_budget_not_exceeded_all_lookups_performed(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""With a large budget, all hover lookups are performed."""
# Create symbol retriever with a mock agent that has large budget
- mock_agent = MagicMock()
- mock_agent.serena_config.symbol_info_budget = 10.0
- mock_agent.get_active_project.return_value = None
+ project_with_ls.serena_config.symbol_info_budget = 10.0
+ project_with_ls.project_config.symbol_info_budget = 10.0
- symbol_retriever = LanguageServerSymbolRetriever(language_server, agent=mock_agent)
+ symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
# Track _request_info calls
call_count = 0
@@ -310,15 +309,13 @@ class TestHoverBudget:
assert all(info is not None for info in result.values())
assert len(result) == 3
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_budget_exceeded_partial_info(self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch):
+ @pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
+ def test_budget_exceeded_partial_info(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""With a small budget, hover lookups stop and remaining symbols get None info."""
- # Create symbol retriever with a mock agent that has small budget (0.1s)
- mock_agent = MagicMock()
- mock_agent.serena_config.symbol_info_budget = 0.1
- mock_agent.get_active_project.return_value = None
+ project_with_ls.serena_config.symbol_info_budget = 0.1
+ project_with_ls.project_config.symbol_info_budget = 0.1
- symbol_retriever = LanguageServerSymbolRetriever(language_server, agent=mock_agent)
+ symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
# Track _request_info calls and simulate 0.05s per call
call_count = 0
@@ -356,15 +353,13 @@ class TestHoverBudget:
assert result_list[3] is None
assert result_list[4] is None
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_budget_zero_means_unlimited(self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch):
+ @pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
+ def test_budget_zero_means_unlimited(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""With budget=0, all hover lookups proceed (no early stopping)."""
- # Create symbol retriever with budget=0 (unlimited)
- mock_agent = MagicMock()
- mock_agent.serena_config.symbol_info_budget = 0.0
- mock_agent.get_active_project.return_value = None
+ project_with_ls.serena_config.symbol_info_budget = 0.0
+ project_with_ls.project_config.symbol_info_budget = 0.0
- symbol_retriever = LanguageServerSymbolRetriever(language_server, agent=mock_agent)
+ symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
# Track _request_info calls
call_count = 0
@@ -385,18 +380,14 @@ class TestHoverBudget:
assert call_count == 5
assert all(info is not None for info in result.values())
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_project_budget_overrides_global(self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch):
+ @pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
+ def test_project_budget_overrides_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""Project-level budget overrides global budget."""
# Create symbol retriever with global budget 10.0 but project budget 0.05
- mock_project = MagicMock()
- mock_project.project_config.symbol_info_budget = 0.05
+ project_with_ls.project_config.symbol_info_budget = 0.05
+ project_with_ls.serena_config.symbol_info_budget = 10.0
- mock_agent = MagicMock()
- mock_agent.serena_config.symbol_info_budget = 10.0
- mock_agent.get_active_project.return_value = mock_project
-
- symbol_retriever = LanguageServerSymbolRetriever(language_server, agent=mock_agent)
+ symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
# Track _request_info calls and simulate time
call_count = 0
@@ -427,18 +418,14 @@ class TestHoverBudget:
# So 2 calls succeed (proving project budget 0.05 overrode global 10.0)
assert call_count == 2
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_project_null_inherits_global(self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch):
+ @pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
+ def test_project_null_inherits_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""When project budget is None, global budget is used."""
# Create symbol retriever with project budget=None (inherit global)
- mock_project = MagicMock()
- mock_project.project_config.symbol_info_budget = None
+ project_with_ls.project_config.symbol_info_budget = None
+ project_with_ls.serena_config.symbol_info_budget = 10.0
- mock_agent = MagicMock()
- mock_agent.serena_config.symbol_info_budget = 10.0
- mock_agent.get_active_project.return_value = mock_project
-
- symbol_retriever = LanguageServerSymbolRetriever(language_server, agent=mock_agent)
+ symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
# Track _request_info calls
call_count = 0
@@ -458,28 +445,3 @@ class TestHoverBudget:
# Global budget is 10s, all 3 should succeed
assert call_count == 3
assert all(info is not None for info in result.values())
-
- @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
- def test_no_agent_uses_default_budget(self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch):
- """When agent is None, default budget of 5s is used."""
- # Create symbol retriever without agent
- symbol_retriever = LanguageServerSymbolRetriever(language_server, agent=None)
-
- # Track _request_info calls
- call_count = 0
-
- def counting_request_info(file_path, line, column, **kwargs):
- nonlocal call_count
- call_count += 1
- return f"info:{line}:{column}"
-
- monkeypatch.setattr(symbol_retriever, "_request_info", counting_request_info)
-
- # Create 3 mock symbols
- symbols = _make_mock_symbols(3)
-
- result = symbol_retriever.request_info_for_symbol_batch(symbols)
-
- # Default budget is 5s, all 3 should succeed
- assert call_count == 3
- assert all(info is not None for info in result.values())
diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py
index 69cd2a77..d6760982 100644
--- a/test/serena/test_symbol_editing.py
+++ b/test/serena/test_symbol_editing.py
@@ -24,7 +24,7 @@ from syrupy import SnapshotAssertion
from serena.code_editor import CodeEditor, LanguageServerCodeEditor
from solidlsp.ls_config import Language
from src.serena.symbol import LanguageServerSymbolRetriever
-from test.conftest import get_repo_path, start_ls_context
+from test.conftest import get_repo_path, project_with_ls_context
pytestmark = pytest.mark.snapshot
@@ -192,7 +192,6 @@ class EditingTest(ABC):
"""Context manager for setup/teardown with a temporary directory, providing the symbol manager."""
temp_dir = Path(tempfile.mkdtemp())
self.repo_path = temp_dir / self.original_repo_path.name
- language_server = None # Initialize language_server
try:
print(f"Copying repo from {self.original_repo_path} to {self.repo_path}")
shutil.copytree(self.original_repo_path, self.repo_path)
@@ -201,8 +200,8 @@ class EditingTest(ABC):
if os.name == "nt":
time.sleep(0.1)
log.info(f"Creating language server for {self.language} {self.rel_path}")
- with start_ls_context(self.language, str(self.repo_path)) as language_server:
- yield LanguageServerSymbolRetriever(ls=language_server)
+ with project_with_ls_context(self.language, str(self.repo_path)) as project:
+ yield LanguageServerSymbolRetriever(project)
finally:
# prevent deadlock on Windows due to lingering file locks
if os.name == "nt":
diff --git a/test/serena/test_text_utils.py b/test/serena/test_text_utils.py
index 0b8dd39d..8a315895 100644
--- a/test/serena/test_text_utils.py
+++ b/test/serena/test_text_utils.py
@@ -346,9 +346,9 @@ class TestSearchFiles:
actual_matched_files = sorted([result.source_file_path for result in results if result.source_file_path])
# Assert that the matched files are exactly the ones expected
- assert actual_matched_files == sorted(
- expected_matched_files
- ), f"Pattern '{paths_include_glob}' failed: expected {sorted(expected_matched_files)}, got {actual_matched_files}"
+ assert actual_matched_files == sorted(expected_matched_files), (
+ f"Pattern '{paths_include_glob}' failed: expected {sorted(expected_matched_files)}, got {actual_matched_files}"
+ )
# Basic check on results structure if files were expected
if expected_matched_files:
diff --git a/test/solidlsp/al/test_al_basic.py b/test/solidlsp/al/test_al_basic.py
index 51bae0b7..c226421f 100644
--- a/test/solidlsp/al/test_al_basic.py
+++ b/test/solidlsp/al/test_al_basic.py
@@ -124,9 +124,9 @@ class TestALLanguageServer:
# Check for codeunit symbols
assert SymbolUtils.symbol_tree_contains_name(symbols, "CustomerMgt"), "CustomerMgt codeunit not found in symbol tree"
- assert SymbolUtils.symbol_tree_contains_name(
- symbols, "PaymentProcessorImpl"
- ), "PaymentProcessorImpl codeunit not found in symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "PaymentProcessorImpl"), (
+ "PaymentProcessorImpl codeunit not found in symbol tree"
+ )
# Check for enum symbol
assert SymbolUtils.symbol_tree_contains_name(symbols, "CustomerType"), "CustomerType enum not found in symbol tree"
@@ -209,14 +209,14 @@ class TestALLanguageServer:
refs = language_server.request_references(table_file, sel_start["line"], sel_start["character"])
# The Customer table should be referenced in CustomerMgt.Codeunit.al
- assert any(
- "CustomerMgt.Codeunit.al" in ref.get("relativePath", "") for ref in refs
- ), "Customer table should be referenced in CustomerMgt.Codeunit.al"
+ assert any("CustomerMgt.Codeunit.al" in ref.get("relativePath", "") for ref in refs), (
+ "Customer table should be referenced in CustomerMgt.Codeunit.al"
+ )
# It should also be referenced in CustomerCard.Page.al
- assert any(
- "CustomerCard.Page.al" in ref.get("relativePath", "") for ref in refs
- ), "Customer table should be referenced in CustomerCard.Page.al"
+ assert any("CustomerCard.Page.al" in ref.get("relativePath", "") for ref in refs), (
+ "Customer table should be referenced in CustomerCard.Page.al"
+ )
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
def test_cross_file_symbols(self, language_server: SolidLanguageServer) -> None:
@@ -392,9 +392,9 @@ class TestALHoverInjection:
assert hover is not None, f"Hover should return a result for {symbol_name}"
assert value is not None, f"Hover should have content for {symbol_name}"
- assert (
- f"**{expected_full_name}**" in value
- ), f"Hover for {symbol_name} should contain '{expected_full_name}'. Got: {value[:200]}"
+ assert f"**{expected_full_name}**" in value, (
+ f"Hover for {symbol_name} should contain '{expected_full_name}'. Got: {value[:200]}"
+ )
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
def test_hover_contains_separator_after_injection(self, language_server: SolidLanguageServer) -> None:
@@ -490,9 +490,9 @@ class TestALPathNormalization:
hover = language_server.request_hover(file_path_forward, line, char)
assert hover is not None, "Hover should return a result"
value = hover.get("contents", {}).get("value", "")
- assert (
- '**Table 50000 "TEST Customer"**' in value
- ), f"Hover injection should work with mixed path formats. Got: {value[:200]}"
+ assert '**Table 50000 "TEST Customer"**' in value, (
+ f"Hover injection should work with mixed path formats. Got: {value[:200]}"
+ )
return
pytest.fail("Could not find TEST Customer symbol")
@@ -517,9 +517,9 @@ class TestALPathNormalization:
hover = language_server.request_hover(file_path_backslash, line, char)
assert hover is not None, "Hover should return a result"
value = hover.get("contents", {}).get("value", "")
- assert (
- '**Table 50000 "TEST Customer"**' in value
- ), f"Hover injection should work with mixed path formats. Got: {value[:200]}"
+ assert '**Table 50000 "TEST Customer"**' in value, (
+ f"Hover injection should work with mixed path formats. Got: {value[:200]}"
+ )
return
pytest.fail("Could not find TEST Customer symbol")
@@ -552,7 +552,7 @@ class TestALPathNormalization:
hover = language_server.request_hover(hover_path, line, char)
assert hover is not None, f"Hover should return a result for {symbol_name}"
value = hover.get("contents", {}).get("value", "")
- assert (
- f"**{expected_injection}**" in value
- ), f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
+ assert f"**{expected_injection}**" in value, (
+ f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
+ )
break
diff --git a/test/solidlsp/ansible/__init__.py b/test/solidlsp/ansible/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/solidlsp/ansible/test_ansible_basic.py b/test/solidlsp/ansible/test_ansible_basic.py
new file mode 100644
index 00000000..154b2231
--- /dev/null
+++ b/test/solidlsp/ansible/test_ansible_basic.py
@@ -0,0 +1,53 @@
+"""
+Basic integration tests for the Ansible language server.
+
+These tests validate initialization, hover, and completion capabilities
+using the standard Ansible test repository. They work with the standard
+@ansible/ansible-language-server from npm.
+"""
+
+import platform
+from pathlib import Path
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.ls_config import Language
+
+
+@pytest.mark.skipif(platform.system() == "Windows", reason="ansible-language-server does not support native Windows operation")
+@pytest.mark.ansible
+class TestAnsibleLanguageServerBasics:
+ """Test basic Ansible language server functionality."""
+
+ @pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.ANSIBLE], indirect=True)
+ def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Language server starts and points to the correct repo."""
+ assert language_server.is_running()
+ assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
+
+ @pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
+ def test_hover_on_module_contains_documentation(self, language_server: SolidLanguageServer) -> None:
+ """Hover on ansible.builtin.package returns module documentation."""
+ # playbook.yml line 10 (0-indexed): "ansible.builtin.package:"
+ result = language_server.request_hover("playbook.yml", 10, 8)
+ assert result is not None, "Expected hover info for ansible.builtin.package"
+ hover_value = result["contents"]
+ if isinstance(hover_value, dict):
+ hover_text = hover_value.get("value", "")
+ elif isinstance(hover_value, list):
+ hover_text = " ".join(str(v) for v in hover_value)
+ else:
+ hover_text = str(hover_value)
+ assert "package" in hover_text.lower(), f"Hover should mention 'package', got: {hover_text[:300]}"
+
+ @pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
+ def test_completions_contain_module_names(self, language_server: SolidLanguageServer) -> None:
+ """Completions at a task keyword position return Ansible module names."""
+ # playbook.yml line 10 (0-indexed), col 6: inside a task block
+ result = language_server.request_completions("playbook.yml", 10, 6)
+ assert result is not None, "Expected completion results"
+ assert len(result) > 0, "Expected non-empty completion list"
+ labels = [item["completionText"] for item in result if "completionText" in item]
+ assert labels, f"Expected completions with completionText, got: {result[:3]}"
diff --git a/test/solidlsp/cpp/test_cpp_basic.py b/test/solidlsp/cpp/test_cpp_basic.py
index 715ef6c9..a8767949 100644
--- a/test/solidlsp/cpp/test_cpp_basic.py
+++ b/test/solidlsp/cpp/test_cpp_basic.py
@@ -135,9 +135,9 @@ int use_add() {
ref_files = [ref.get("relativePath", "") for ref in refs]
# Should find reference in the newly written file
- assert any(
- "temp_new_file.cpp" in ref_file for ref_file in ref_files
- ), f"Should find reference in newly written temp_new_file.cpp, {ref_files=}"
+ assert any("temp_new_file.cpp" in ref_file for ref_file in ref_files), (
+ f"Should find reference in newly written temp_new_file.cpp, {ref_files=}"
+ )
finally:
# Clean up the new file
if os.path.exists(new_file_abs_path):
diff --git a/test/solidlsp/csharp/test_csharp_basic.py b/test/solidlsp/csharp/test_csharp_basic.py
index 51e4f9b1..3d538061 100644
--- a/test/solidlsp/csharp/test_csharp_basic.py
+++ b/test/solidlsp/csharp/test_csharp_basic.py
@@ -63,9 +63,9 @@ class TestCSharpLanguageServer:
assert add_symbol is not None, "Could not find 'Add' method symbol in Program.cs"
sel_start = add_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1)
- assert any(
- "Program.cs" in ref.get("relativePath", "") for ref in refs
- ), "Program.cs should reference Add method (tried all positions in selectionRange)"
+ assert any("Program.cs" in ref.get("relativePath", "") for ref in refs), (
+ "Program.cs should reference Add method (tried all positions in selectionRange)"
+ )
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True)
def test_nested_namespace_symbols(self, language_server: SolidLanguageServer) -> None:
@@ -121,9 +121,9 @@ class TestCSharpLanguageServer:
# Check that we have reference in Models/Person.cs where Calculator.Subtract is called
# Note: New Roslyn version doesn't include the definition itself as a reference (more correct behavior)
- assert any(
- os.path.join("Models", "Person.cs") in ref_file for ref_file in ref_files
- ), "Should find reference in Models/Person.cs where Calculator.Subtract is called"
+ assert any(os.path.join("Models", "Person.cs") in ref_file for ref_file in ref_files), (
+ "Should find reference in Models/Person.cs where Calculator.Subtract is called"
+ )
assert len(refs) > 0, "Should find at least one reference"
# check for a second time, since the first call may trigger initialization and change the state of the LS
@@ -302,7 +302,7 @@ class TestCSharpSolutionProjectOpening:
# Create CSharpLanguageServer instance
mock_settings = Mock(spec=SolidLSPSettings)
mock_settings.ls_resources_dir = "/tmp/test_ls_resources"
- mock_settings.project_data_relative_path = "project_data"
+ mock_settings.project_data_path = str(temp_path / "project_data")
with SuspendedLoggersContext():
logging.getLogger().setLevel(logging.DEBUG)
@@ -330,7 +330,7 @@ class TestCSharpSolutionProjectOpening:
mock_settings = Mock(spec=SolidLSPSettings)
mock_settings.ls_resources_dir = "/tmp/test_ls_resources"
- mock_settings.project_data_relative_path = "project_data"
+ mock_settings.project_data_path = str(temp_path / "project_data")
# Create CSharpLanguageServer instance
with SuspendedLoggersContext():
diff --git a/test/solidlsp/csharp/test_csharp_dotnet_install.py b/test/solidlsp/csharp/test_csharp_dotnet_install.py
deleted file mode 100644
index f59db6e5..00000000
--- a/test/solidlsp/csharp/test_csharp_dotnet_install.py
+++ /dev/null
@@ -1,290 +0,0 @@
-"""Tests for C# language server .NET runtime installation using official install scripts."""
-
-import tempfile
-from pathlib import Path
-from unittest.mock import Mock, patch
-
-import pytest
-
-from solidlsp.language_servers.csharp_language_server import CSharpLanguageServer
-from solidlsp.settings import SolidLSPSettings
-
-
-@pytest.mark.csharp
-class TestDotNetInstallScript:
- """Test .NET runtime installation using Microsoft's official install scripts."""
-
- @patch("solidlsp.language_servers.csharp_language_server.CSharpLanguageServer.DependencyProvider._ensure_language_server")
- @patch("solidlsp.language_servers.common.PlatformUtils.get_platform_id")
- @patch("solidlsp.language_servers.csharp_language_server.platform.system")
- @patch("solidlsp.language_servers.csharp_language_server.subprocess.run")
- @patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlretrieve")
- @patch("solidlsp.language_servers.csharp_language_server.shutil.which")
- def test_install_dotnet_uses_bash_script_on_linux(
- self, mock_which, mock_urlretrieve, mock_subprocess, mock_platform, mock_platform_id, mock_ensure_ls
- ):
- """Test that Linux uses bash install script."""
- from solidlsp.ls_utils import PlatformId
-
- mock_platform.return_value = "Linux"
- mock_platform_id.return_value = PlatformId.LINUX_x64
- mock_which.return_value = None # No system dotnet
- mock_ensure_ls.return_value = "/fake/server.dll"
-
- with tempfile.TemporaryDirectory() as temp_dir:
-
- def mock_retrieve(url, path):
- # Create the file that urlretrieve would create
- Path(path).touch()
- # Also create the dotnet executable after script download
- if "dotnet-install.sh" in str(path):
- dotnet_dir = Path(temp_dir) / "dotnet-runtime-10.0"
- dotnet_dir.mkdir(parents=True, exist_ok=True)
- dotnet_exe = dotnet_dir / "dotnet"
- dotnet_exe.touch()
- dotnet_exe.chmod(0o755)
-
- mock_urlretrieve.side_effect = mock_retrieve
- mock_subprocess.return_value = Mock(returncode=0, stdout="", stderr="")
-
- mock_settings = SolidLSPSettings()
- custom_settings = SolidLSPSettings.CustomLSSettings({})
-
- _ = CSharpLanguageServer.DependencyProvider(
- custom_settings=custom_settings,
- ls_resources_dir=temp_dir,
- solidlsp_settings=mock_settings,
- repository_root_path="/fake/repo",
- )
-
- # Verify bash script was downloaded
- assert mock_urlretrieve.called
- script_url = mock_urlretrieve.call_args[0][0]
- assert script_url == "https://dot.net/v1/dotnet-install.sh"
-
- @patch("solidlsp.language_servers.csharp_language_server.CSharpLanguageServer.DependencyProvider._ensure_language_server")
- @patch("solidlsp.language_servers.common.PlatformUtils.get_platform_id")
- @patch("solidlsp.language_servers.csharp_language_server.platform.system")
- @patch("solidlsp.language_servers.csharp_language_server.subprocess.run")
- @patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlretrieve")
- @patch("solidlsp.language_servers.csharp_language_server.shutil.which")
- def test_install_dotnet_uses_powershell_script_on_windows(
- self, mock_which, mock_urlretrieve, mock_subprocess, mock_platform, mock_platform_id, mock_ensure_ls
- ):
- """Test that Windows uses PowerShell install script."""
- from solidlsp.ls_utils import PlatformId
-
- mock_platform.return_value = "Windows"
- mock_platform_id.return_value = PlatformId.WIN_x64
- mock_which.return_value = None # No system dotnet
- mock_ensure_ls.return_value = "/fake/server.dll"
-
- with tempfile.TemporaryDirectory() as temp_dir:
-
- def mock_retrieve(url, path):
- # Create the file that urlretrieve would create
- Path(path).touch()
- # Also create the dotnet executable after script download
- if "dotnet-install.ps1" in str(path):
- dotnet_dir = Path(temp_dir) / "dotnet-runtime-10.0"
- dotnet_dir.mkdir(parents=True, exist_ok=True)
- dotnet_exe = dotnet_dir / "dotnet.exe"
- dotnet_exe.touch()
-
- mock_urlretrieve.side_effect = mock_retrieve
- mock_subprocess.return_value = Mock(returncode=0, stdout="", stderr="")
-
- mock_settings = SolidLSPSettings()
- custom_settings = SolidLSPSettings.CustomLSSettings({})
-
- _ = CSharpLanguageServer.DependencyProvider(
- custom_settings=custom_settings,
- ls_resources_dir=temp_dir,
- solidlsp_settings=mock_settings,
- repository_root_path="/fake/repo",
- )
-
- # Verify PowerShell script was downloaded
- assert mock_urlretrieve.called
- script_url = mock_urlretrieve.call_args[0][0]
- assert script_url == "https://dot.net/v1/dotnet-install.ps1"
-
- def test_uses_system_dotnet_10_if_available(self):
- """Test that system .NET 10 is used if available instead of downloading."""
- from solidlsp.ls_utils import PlatformId
-
- with patch("solidlsp.language_servers.csharp_language_server.shutil.which") as mock_which:
- with patch("solidlsp.language_servers.csharp_language_server.subprocess.run") as mock_subprocess:
- with patch("solidlsp.language_servers.common.PlatformUtils.get_platform_id") as mock_platform_id:
- with patch(
- "solidlsp.language_servers.csharp_language_server.CSharpLanguageServer.DependencyProvider._ensure_language_server"
- ) as mock_ensure_ls:
- mock_which.return_value = "/usr/bin/dotnet"
- mock_ensure_ls.return_value = "/fake/server.dll"
- mock_platform_id.return_value = PlatformId.LINUX_x64
-
- # Mock dotnet --list-runtimes output showing .NET 10
- mock_result = Mock()
- mock_result.stdout = "Microsoft.NETCore.App 10.0.1 [/usr/share/dotnet/shared/Microsoft.NETCore.App]"
- mock_result.returncode = 0
- mock_subprocess.return_value = mock_result
-
- with tempfile.TemporaryDirectory() as temp_dir:
- mock_settings = SolidLSPSettings()
- custom_settings = SolidLSPSettings.CustomLSSettings({})
-
- dependency_provider = CSharpLanguageServer.DependencyProvider(
- custom_settings=custom_settings,
- ls_resources_dir=temp_dir,
- solidlsp_settings=mock_settings,
- repository_root_path="/fake/repo",
- )
-
- # Should have used system dotnet
- assert dependency_provider._dotnet_path == "/usr/bin/dotnet"
-
- def test_runtime_dependencies_no_longer_include_dotnet_downloads(self):
- """Test that _RUNTIME_DEPENDENCIES no longer includes manual .NET download entries."""
- from solidlsp.language_servers.csharp_language_server import _RUNTIME_DEPENDENCIES
-
- # Check that DotNetRuntime entries don't exist (we use install script instead)
- dotnet_deps = [dep for dep in _RUNTIME_DEPENDENCIES if dep.id == "DotNetRuntime"]
-
- # After our changes, there should be no DotNetRuntime entries
- # (we'll use the install script instead)
- assert len(dotnet_deps) == 0, "DotNetRuntime dependencies should be removed in favor of install script"
-
- @patch("solidlsp.language_servers.csharp_language_server.CSharpLanguageServer.DependencyProvider._ensure_language_server")
- @patch("solidlsp.language_servers.common.PlatformUtils.get_platform_id")
- @patch("solidlsp.language_servers.csharp_language_server.platform.system")
- @patch("solidlsp.language_servers.csharp_language_server.subprocess.run")
- @patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlretrieve")
- @patch("solidlsp.language_servers.csharp_language_server.shutil.which")
- def test_cached_dotnet_is_reused(self, mock_which, mock_urlretrieve, mock_subprocess, mock_platform, mock_platform_id, mock_ensure_ls):
- """Test that cached .NET installation is reused without re-downloading."""
- from solidlsp.ls_utils import PlatformId
-
- mock_platform.return_value = "Linux"
- mock_platform_id.return_value = PlatformId.LINUX_x64
- mock_which.return_value = None
- mock_ensure_ls.return_value = "/fake/server.dll"
-
- with tempfile.TemporaryDirectory() as temp_dir:
- # Pre-create the cached dotnet executable
- dotnet_dir = Path(temp_dir) / "dotnet-runtime-10.0"
- dotnet_dir.mkdir(parents=True, exist_ok=True)
- dotnet_exe = dotnet_dir / "dotnet"
- dotnet_exe.touch()
- dotnet_exe.chmod(0o755)
-
- mock_settings = SolidLSPSettings()
- custom_settings = SolidLSPSettings.CustomLSSettings({})
-
- dependency_provider = CSharpLanguageServer.DependencyProvider(
- custom_settings=custom_settings,
- ls_resources_dir=temp_dir,
- solidlsp_settings=mock_settings,
- repository_root_path="/fake/repo",
- )
-
- # Should use cached dotnet without downloading install script
- assert dependency_provider._dotnet_path == str(dotnet_exe)
- assert not mock_urlretrieve.called, "Should not download install script when cached dotnet exists"
-
- def test_rejects_dotnet_9_and_installs_dotnet_10(self):
- """Test that .NET 9 is rejected and .NET 10 install is triggered."""
- from solidlsp.ls_utils import PlatformId
-
- with patch("solidlsp.language_servers.csharp_language_server.shutil.which") as mock_which:
- with patch("solidlsp.language_servers.csharp_language_server.subprocess.run") as mock_subprocess:
- with patch("solidlsp.language_servers.common.PlatformUtils.get_platform_id") as mock_platform_id:
- with patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlretrieve") as mock_urlretrieve:
- with patch("solidlsp.language_servers.csharp_language_server.platform.system") as mock_platform:
- with patch(
- "solidlsp.language_servers.csharp_language_server.CSharpLanguageServer.DependencyProvider._ensure_language_server"
- ) as mock_ensure_ls:
- mock_which.return_value = "/usr/bin/dotnet"
- mock_ensure_ls.return_value = "/fake/server.dll"
- mock_platform_id.return_value = PlatformId.LINUX_x64
- mock_platform.return_value = "Linux"
-
- # Mock dotnet --list-runtimes output showing only .NET 9
- mock_result = Mock()
- mock_result.stdout = "Microsoft.NETCore.App 9.0.1 [/usr/share/dotnet/shared/Microsoft.NETCore.App]"
- mock_result.returncode = 0
-
- call_count = 0
-
- def subprocess_side_effect(*args, **kwargs):
- nonlocal call_count
- call_count += 1
- if call_count == 1:
- # First call: dotnet --list-runtimes returns .NET 9
- return mock_result
- else:
- # Second call: install script execution
- return Mock(returncode=0, stdout="", stderr="")
-
- mock_subprocess.side_effect = subprocess_side_effect
-
- with tempfile.TemporaryDirectory() as temp_dir:
-
- def mock_retrieve(url, path):
- Path(path).touch()
- if "dotnet-install.sh" in str(path):
- dotnet_dir = Path(temp_dir) / "dotnet-runtime-10.0"
- dotnet_dir.mkdir(parents=True, exist_ok=True)
- dotnet_exe = dotnet_dir / "dotnet"
- dotnet_exe.touch()
- dotnet_exe.chmod(0o755)
-
- mock_urlretrieve.side_effect = mock_retrieve
-
- mock_settings = SolidLSPSettings()
- custom_settings = SolidLSPSettings.CustomLSSettings({})
-
- dependency_provider = CSharpLanguageServer.DependencyProvider(
- custom_settings=custom_settings,
- ls_resources_dir=temp_dir,
- solidlsp_settings=mock_settings,
- repository_root_path="/fake/repo",
- )
-
- # Should have installed .NET 10 (not used system .NET 9)
- assert dependency_provider._dotnet_path == str(Path(temp_dir) / "dotnet-runtime-10.0" / "dotnet")
- assert mock_urlretrieve.called, "Should download install script when .NET 9 found"
-
- def test_accepts_dotnet_11_and_higher(self):
- """Test that .NET 11+ versions are accepted for forward compatibility."""
- from solidlsp.ls_utils import PlatformId
-
- for version in ["11.0.1", "12.0.0", "15.3.2"]:
- with patch("solidlsp.language_servers.csharp_language_server.shutil.which") as mock_which:
- with patch("solidlsp.language_servers.csharp_language_server.subprocess.run") as mock_subprocess:
- with patch("solidlsp.language_servers.common.PlatformUtils.get_platform_id") as mock_platform_id:
- with patch(
- "solidlsp.language_servers.csharp_language_server.CSharpLanguageServer.DependencyProvider._ensure_language_server"
- ) as mock_ensure_ls:
- mock_which.return_value = "/usr/bin/dotnet"
- mock_ensure_ls.return_value = "/fake/server.dll"
- mock_platform_id.return_value = PlatformId.LINUX_x64
-
- # Mock dotnet --list-runtimes output showing .NET 11+
- mock_result = Mock()
- mock_result.stdout = f"Microsoft.NETCore.App {version} [/usr/share/dotnet/shared/Microsoft.NETCore.App]"
- mock_result.returncode = 0
- mock_subprocess.return_value = mock_result
-
- with tempfile.TemporaryDirectory() as temp_dir:
- mock_settings = SolidLSPSettings()
- custom_settings = SolidLSPSettings.CustomLSSettings({})
-
- dependency_provider = CSharpLanguageServer.DependencyProvider(
- custom_settings=custom_settings,
- ls_resources_dir=temp_dir,
- solidlsp_settings=mock_settings,
- repository_root_path="/fake/repo",
- )
-
- # Should use system dotnet for .NET 11+
- assert dependency_provider._dotnet_path == "/usr/bin/dotnet", f"Should accept .NET {version}"
diff --git a/test/solidlsp/csharp/test_csharp_nuget_download.py b/test/solidlsp/csharp/test_csharp_nuget_download.py
index 4f9a007c..09b7eaed 100644
--- a/test/solidlsp/csharp/test_csharp_nuget_download.py
+++ b/test/solidlsp/csharp/test_csharp_nuget_download.py
@@ -69,9 +69,9 @@ class TestNuGetOrgDownload:
for dep in lang_server_deps:
# Verify package name uses roslyn-language-server
assert dep.package_name is not None, f"Package name should be set for {dep.platform_id}"
- assert dep.package_name.startswith(
- "roslyn-language-server."
- ), f"Package name should start with 'roslyn-language-server.' but got: {dep.package_name}"
+ assert dep.package_name.startswith("roslyn-language-server."), (
+ f"Package name should start with 'roslyn-language-server.' but got: {dep.package_name}"
+ )
# Verify version is the newer NuGet.org version
assert dep.package_version == "5.5.0-2.26078.4", f"Should use NuGet.org version 5.5.0-2.26078.4, got: {dep.package_version}"
diff --git a/test/solidlsp/dart/test_dart_basic.py b/test/solidlsp/dart/test_dart_basic.py
index f469e8cf..450f99c4 100644
--- a/test/solidlsp/dart/test_dart_basic.py
+++ b/test/solidlsp/dart/test_dart_basic.py
@@ -196,9 +196,9 @@ class TestDartLanguageServer:
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
# Check that we found references - at least one should be in main.dart
- assert any(
- "main.dart" in ref.get("relativePath", "") or "main.dart" in ref.get("uri", "") for ref in refs
- ), "main.dart should reference add method (tried all positions in selectionRange)"
+ assert any("main.dart" in ref.get("relativePath", "") or "main.dart" in ref.get("uri", "") for ref in refs), (
+ "main.dart should reference add method (tried all positions in selectionRange)"
+ )
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/elixir/test_elixir_ignored_dirs.py b/test/solidlsp/elixir/test_elixir_ignored_dirs.py
index d74c58d2..b01531ba 100644
--- a/test/solidlsp/elixir/test_elixir_ignored_dirs.py
+++ b/test/solidlsp/elixir/test_elixir_ignored_dirs.py
@@ -92,7 +92,6 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
"""
ignored_paths = ["*cripts", "ignored_*"] # codespell:ignore cripts
with start_ls_context(language=Language.ELIXIR, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
-
# Same as in the above tests
root = ls.request_full_symbol_tree()[0]
root_children = root["children"]
diff --git a/test/solidlsp/erlang/test_erlang_symbol_retrieval.py b/test/solidlsp/erlang/test_erlang_symbol_retrieval.py
index d63c0206..b2f9fce4 100644
--- a/test/solidlsp/erlang/test_erlang_symbol_retrieval.py
+++ b/test/solidlsp/erlang/test_erlang_symbol_retrieval.py
@@ -308,8 +308,7 @@ class TestErlangLanguageServerSymbols:
@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.",
+ reason="Known intermittent timeout issue in Erlang LS in CI environments. May pass locally but can timeout on slower CI systems.",
strict=False,
)
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
diff --git a/test/solidlsp/fortran/test_fortran_basic.py b/test/solidlsp/fortran/test_fortran_basic.py
index e4ea280d..1554e959 100644
--- a/test/solidlsp/fortran/test_fortran_basic.py
+++ b/test/solidlsp/fortran/test_fortran_basic.py
@@ -83,9 +83,9 @@ class TestFortranLanguageServer:
# 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]}"
+ 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:
@@ -104,14 +104,14 @@ class TestFortranLanguageServer:
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')}"
+ 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']}"
+ 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:
@@ -202,14 +202,14 @@ class TestFortranLanguageServer:
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')}'"
+ 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')}"
+ 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"
@@ -264,9 +264,9 @@ class TestFortranLanguageServer:
# 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']}"
+ 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"])
diff --git a/test/solidlsp/fsharp/test_fsharp_basic.py b/test/solidlsp/fsharp/test_fsharp_basic.py
index 578ae704..8e6ff221 100644
--- a/test/solidlsp/fsharp/test_fsharp_basic.py
+++ b/test/solidlsp/fsharp/test_fsharp_basic.py
@@ -1,20 +1,18 @@
import os
-import tempfile
import threading
-from pathlib import Path
from typing import Any
-from unittest.mock import Mock, patch
import pytest
from solidlsp import SolidLanguageServer
-from solidlsp.language_servers.fsharp_language_server import FSharpLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import is_ci
+# Currently, most F# tests fail, there seems to be a regression or instability.
@pytest.mark.fsharp
+@pytest.mark.skipif(is_ci, reason="F# language server is currently unreliable")
class TestFSharpLanguageServer:
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
@@ -53,6 +51,7 @@ class TestFSharpLanguageServer:
for expected in expected_symbols:
assert expected in symbol_names, f"{expected} function not found in Calculator.fs symbols"
+ @pytest.mark.xfail(is_ci, reason="Test is flaky") # TODO: Re-enable if the LS can be made more reliable #1040
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test finding references using symbol selection range."""
@@ -112,6 +111,7 @@ class TestFSharpLanguageServer:
# The subtract function should be referenced in Program.fs
assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference subtract function"
+ @pytest.mark.xfail(is_ci, reason="Test is flaky") # TODO: Re-enable if the LS can be made more reliable #1040
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
def test_go_to_definition(self, language_server: SolidLanguageServer) -> None:
"""Test go-to-definition functionality."""
@@ -183,41 +183,3 @@ class TestFSharpLanguageServer:
# This is a successful test - FsAutoComplete is working with F# files
assert True, "F# language server can handle files successfully"
-
-
-@pytest.mark.fsharp
-class TestFSharpLanguageServerSetup:
- """Test F# language server setup and configuration."""
-
- def test_runtime_dependency_setup_without_dotnet(self) -> None:
- """Test that setup fails gracefully when .NET is not installed."""
- with patch("shutil.which", return_value=None):
- with pytest.raises(RuntimeError, match=r"\.NET SDK is not installed"):
- FSharpLanguageServer._setup_runtime_dependencies(Mock(), Mock())
-
- def test_runtime_dependency_setup_with_dotnet(self) -> None:
- """Test that setup works when .NET is available."""
- mock_config = Mock()
- mock_settings = Mock()
-
- # Mock the ls_resources_dir to return a temp directory
- with tempfile.TemporaryDirectory() as temp_dir:
- with patch("shutil.which", return_value="/usr/bin/dotnet"):
- with patch.object(FSharpLanguageServer, "ls_resources_dir", return_value=temp_dir):
- with patch("subprocess.run") as mock_run:
- # Mock successful dotnet version check
- mock_run.return_value.stdout = "8.0.100"
- mock_run.return_value.returncode = 0
-
- # Create a fake fsautocomplete executable
- fsharp_dir = os.path.join(temp_dir, "fsharp-lsp")
- os.makedirs(fsharp_dir, exist_ok=True)
- # Use .exe extension on Windows, matching production code
- exe_name = "fsautocomplete.exe" if os.name == "nt" else "fsautocomplete"
- fsautocomplete_path = os.path.join(fsharp_dir, exe_name)
- Path(fsautocomplete_path).touch()
-
- result = FSharpLanguageServer._setup_runtime_dependencies(mock_config, mock_settings)
-
- assert fsautocomplete_path in result
- assert "--adaptive-lsp-server-enabled --project-graph-enabled --use-fcs-transparent-compiler" in result
diff --git a/test/solidlsp/go/test_go_basic.py b/test/solidlsp/go/test_go_basic.py
index cd7e8a86..8d83298c 100644
--- a/test/solidlsp/go/test_go_basic.py
+++ b/test/solidlsp/go/test_go_basic.py
@@ -154,9 +154,9 @@ class TestGoBuildTags:
assert versioned_cache_files, f"Expected at least one SolidLSP cache file with a __cache_version under {cache_dir}"
saved_versions = {v for _, v in versioned_cache_files}
- assert (
- default_raw_cache_version in saved_versions or default_doc_cache_version in saved_versions
- ), "Expected at least one persisted cache to match the default-context cache version"
+ assert default_raw_cache_version in saved_versions or default_doc_cache_version in saved_versions, (
+ "Expected at least one persisted cache to match the default-context cache version"
+ )
# Run 2 (default context again): prove that persisted caches are actually loaded and used.
with start_ls_context(Language.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default_again:
diff --git a/test/solidlsp/groovy/test_groovy_basic.py b/test/solidlsp/groovy/test_groovy_basic.py
index 8792f1dc..57e0baf3 100644
--- a/test/solidlsp/groovy/test_groovy_basic.py
+++ b/test/solidlsp/groovy/test_groovy_basic.py
@@ -43,9 +43,10 @@ class TestGroovyLanguageServer:
repo_path = str(cls.test_repo_path)
config = LanguageServerConfig(code_language=Language.GROOVY, ignored_paths=[], trace_lsp_communication=False)
+ project_data_path = os.path.join(repo_path, SERENA_MANAGED_DIR_NAME)
solidlsp_settings = SolidLSPSettings(
solidlsp_dir=str(Path.home() / ".serena"),
- project_data_relative_path=SERENA_MANAGED_DIR_NAME,
+ project_data_path=project_data_path,
ls_specific_settings={Language.GROOVY: groovy_settings},
)
diff --git a/test/solidlsp/haskell/test_haskell_basic.py b/test/solidlsp/haskell/test_haskell_basic.py
index 97820643..b080a306 100644
--- a/test/solidlsp/haskell/test_haskell_basic.py
+++ b/test/solidlsp/haskell/test_haskell_basic.py
@@ -145,9 +145,9 @@ class TestHaskellLanguageServer:
# 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}"
+ 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):
@@ -168,9 +168,9 @@ class TestHaskellLanguageServer:
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}"
+ 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):
@@ -191,9 +191,9 @@ class TestHaskellLanguageServer:
# 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}"
+ 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):
@@ -214,6 +214,6 @@ class TestHaskellLanguageServer:
# 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}"
+ 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}"
+ )
diff --git a/test/solidlsp/hlsl/__init__.py b/test/solidlsp/hlsl/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/solidlsp/hlsl/__init__.py
@@ -0,0 +1 @@
+
diff --git a/test/solidlsp/hlsl/test_hlsl_basic.py b/test/solidlsp/hlsl/test_hlsl_basic.py
new file mode 100644
index 00000000..7034579d
--- /dev/null
+++ b/test/solidlsp/hlsl/test_hlsl_basic.py
@@ -0,0 +1,160 @@
+"""
+Basic tests for HLSL language server integration (shader-language-server).
+
+This module tests Language.HLSL using shader-language-server from antaalt/shader-sense.
+Tests are skipped if the language server is not available.
+"""
+
+from typing import Any
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.ls_config import Language
+from solidlsp.ls_exceptions import SolidLSPException
+from solidlsp.ls_utils import SymbolUtils
+
+
+def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
+ """Find a top-level symbol by name in a file's document symbols."""
+ symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
+ return next((s for s in symbols[0] if s.get("name") == name), None)
+
+
+# ── Symbol Discovery ─────────────────────────────────────────────
+
+
+@pytest.mark.hlsl
+class TestHlslSymbols:
+ """Tests for document symbol extraction."""
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_find_struct(self, language_server: SolidLanguageServer) -> None:
+ """VertexInput struct should appear in common.hlsl symbols."""
+ symbol = _find_symbol_by_name(language_server, "common.hlsl", "VertexInput")
+ assert symbol is not None, "Expected 'VertexInput' struct in document symbols"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_find_function(self, language_server: SolidLanguageServer) -> None:
+ """SafeNormalize function should appear in common.hlsl."""
+ symbol = _find_symbol_by_name(language_server, "common.hlsl", "SafeNormalize")
+ assert symbol is not None, "Expected 'SafeNormalize' function in document symbols"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_find_cbuffer_members(self, language_server: SolidLanguageServer) -> None:
+ """Cbuffer members should appear as variables in compute_test.hlsl.
+
+ Note: shader-language-server reports cbuffer members as individual
+ variables (kind 13), not the cbuffer name itself as a symbol.
+ """
+ symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "TextureSize")
+ assert symbol is not None, "Expected 'TextureSize' cbuffer member in document symbols"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_find_compute_kernel(self, language_server: SolidLanguageServer) -> None:
+ """CSMain kernel should appear in compute_test.hlsl."""
+ symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "CSMain")
+ assert symbol is not None, "Expected 'CSMain' compute kernel in document symbols"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
+ """Full symbol tree should contain symbols from multiple files."""
+ symbols = language_server.request_full_symbol_tree()
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "VertexInput"), "VertexInput not in symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculateDiffuse"), "CalculateDiffuse not in symbol tree"
+
+
+# ── Go-to-Definition ─────────────────────────────────────────────
+
+
+@pytest.mark.hlsl
+class TestHlslDefinition:
+ """Tests for go-to-definition capability."""
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
+ """Navigating to SafeNormalize call in lighting.hlsl should resolve to common.hlsl.
+
+ lighting.hlsl line 22 (0-indexed): " float3 halfVec = SafeNormalize(-lightDir + viewDir);"
+ SafeNormalize starts at column 21.
+ """
+ definitions = language_server.request_definition("lighting.hlsl", 22, 21)
+ assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
+ def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
+ assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_goto_definition_cross_file_remap(self, language_server: SolidLanguageServer) -> None:
+ """Navigating to Remap call in compute_test.hlsl should resolve to common.hlsl.
+
+ compute_test.hlsl line 20 (0-indexed): " Remap(color.r, 0.0, 1.0, 0.2, 0.8),"
+ Remap starts at column 8.
+ """
+ definitions = language_server.request_definition("compute_test.hlsl", 20, 8)
+ assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
+ def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
+ assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
+
+
+# ── References ────────────────────────────────────────────────────
+
+
+@pytest.mark.hlsl
+class TestHlslReferences:
+ """Tests for find-references capability.
+
+ shader-language-server does not advertise referencesProvider, so
+ request_references is expected to return an empty list.
+ """
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_references_not_supported(self, language_server: SolidLanguageServer) -> None:
+ """References request should raise because shader-language-server does not support it.
+
+ common.hlsl line 17 (0-indexed): "float3 SafeNormalize(float3 v)"
+ SafeNormalize starts at column 7.
+ """
+ with pytest.raises(SolidLSPException, match="Method not found"):
+ language_server.request_references("common.hlsl", 17, 7)
+
+
+# ── Hover ─────────────────────────────────────────────────────────
+
+
+def _extract_hover_text(hover_info: dict[str, Any]) -> str:
+ """Extract the text content from an LSP hover response."""
+ contents = hover_info["contents"]
+ if isinstance(contents, dict):
+ return contents.get("value", "")
+ elif isinstance(contents, str):
+ return contents
+ return str(contents)
+
+
+@pytest.mark.hlsl
+class TestHlslHover:
+ """Tests for hover information."""
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_hover_on_function(self, language_server: SolidLanguageServer) -> None:
+ """Hovering over SafeNormalize definition should return info.
+
+ common.hlsl line 17 (0-indexed): "float3 SafeNormalize(float3 v)"
+ SafeNormalize starts at column 7.
+ """
+ hover_info = language_server.request_hover("common.hlsl", 17, 7)
+ assert hover_info is not None, "Hover should return information for SafeNormalize"
+ assert "contents" in hover_info, "Hover should have contents"
+ hover_text = _extract_hover_text(hover_info)
+ assert len(hover_text) > 0, "Hover text should not be empty"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_hover_on_struct(self, language_server: SolidLanguageServer) -> None:
+ """Hovering over VertexInput should return struct info.
+
+ common.hlsl line 3 (0-indexed): "struct VertexInput"
+ VertexInput starts at column 7.
+ """
+ hover_info = language_server.request_hover("common.hlsl", 3, 7)
+ assert hover_info is not None, "Hover should return information for VertexInput"
+ assert "contents" in hover_info, "Hover should have contents"
diff --git a/test/solidlsp/hlsl/test_hlsl_full_index.py b/test/solidlsp/hlsl/test_hlsl_full_index.py
new file mode 100644
index 00000000..70df1a61
--- /dev/null
+++ b/test/solidlsp/hlsl/test_hlsl_full_index.py
@@ -0,0 +1,63 @@
+"""
+Regression tests for HLSL full symbol tree indexing.
+
+These tests verify that request_full_symbol_tree() correctly indexes all files,
+including .hlsl includes in subdirectories. This catches bugs where files are
+silently dropped during workspace-wide indexing.
+"""
+
+from typing import Any
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.ls_config import Language
+from solidlsp.ls_types import SymbolKind
+from solidlsp.ls_utils import SymbolUtils
+
+
+def _collect_file_names(symbols: list[dict[str, Any]]) -> set[str]:
+ """Recursively collect the names of all File-kind symbols in the tree."""
+ names: set[str] = set()
+ for sym in symbols:
+ if sym.get("kind") == SymbolKind.File:
+ names.add(sym["name"])
+ if "children" in sym:
+ names.update(_collect_file_names(sym["children"]))
+ return names
+
+
+EXPECTED_FILES = {"common", "lighting", "compute_test", "terrain_sdf"}
+
+TERRAIN_SDF_UNIQUE_SYMBOLS = {"SampleSDF", "CalculateGradient", "SDFBrickData"}
+
+
+@pytest.mark.hlsl
+class TestHlslFullIndex:
+ """Tests for full symbol tree indexing completeness."""
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_all_files_indexed_in_symbol_tree(self, language_server: SolidLanguageServer) -> None:
+ """Every .hlsl file in the test repo must appear as a File symbol in the tree."""
+ symbols = language_server.request_full_symbol_tree()
+ file_names = _collect_file_names(symbols)
+ missing = EXPECTED_FILES - file_names
+ assert not missing, f"Files missing from full symbol tree: {missing}. Found: {file_names}"
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_subdirectory_file_symbols_present(self, language_server: SolidLanguageServer) -> None:
+ """Symbols unique to terrain/terrain_sdf.hlsl must appear in the full tree."""
+ symbols = language_server.request_full_symbol_tree()
+ for name in TERRAIN_SDF_UNIQUE_SYMBOLS:
+ assert SymbolUtils.symbol_tree_contains_name(symbols, name), (
+ f"Expected '{name}' from terrain/terrain_sdf.hlsl in full symbol tree"
+ )
+
+ @pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
+ def test_include_file_document_symbols_directly(self, language_server: SolidLanguageServer) -> None:
+ """request_document_symbols on terrain/terrain_sdf.hlsl should return its symbols."""
+ doc_symbols = language_server.request_document_symbols("terrain/terrain_sdf.hlsl")
+ all_symbols = doc_symbols.get_all_symbols_and_roots()
+ symbol_names = {s.get("name") for s in all_symbols[0]}
+ for name in TERRAIN_SDF_UNIQUE_SYMBOLS:
+ assert name in symbol_names, f"Expected '{name}' in document symbols for terrain/terrain_sdf.hlsl, got: {symbol_names}"
diff --git a/test/solidlsp/java/test_java_basic.py b/test/solidlsp/java/test_java_basic.py
index 57642bbe..0810dac2 100644
--- a/test/solidlsp/java/test_java_basic.py
+++ b/test/solidlsp/java/test_java_basic.py
@@ -40,9 +40,9 @@ class TestJavaLanguageServer:
else:
sel_start = model_symbol["range"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
- assert any(
- "Main.java" in ref.get("relativePath", "") for ref in refs
- ), "Main should reference Model (tried all positions in selectionRange)"
+ assert any("Main.java" in ref.get("relativePath", "") for ref in refs), (
+ "Main should reference Model (tried all positions in selectionRange)"
+ )
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/kotlin/test_kotlin_basic.py b/test/solidlsp/kotlin/test_kotlin_basic.py
index f6a135d8..762c391c 100644
--- a/test/solidlsp/kotlin/test_kotlin_basic.py
+++ b/test/solidlsp/kotlin/test_kotlin_basic.py
@@ -45,9 +45,9 @@ class TestKotlinLanguageServer:
else:
sel_start = model_symbol["range"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
- assert any(
- "Main.kt" in ref.get("relativePath", "") for ref in refs
- ), "Main should reference Model (tried all positions in selectionRange)"
+ assert any("Main.kt" in ref.get("relativePath", "") for ref in refs), (
+ "Main should reference Model (tried all positions in selectionRange)"
+ )
@pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/lean4/test_lean4_basic.py b/test/solidlsp/lean4/test_lean4_basic.py
new file mode 100644
index 00000000..b8f35efa
--- /dev/null
+++ b/test/solidlsp/lean4/test_lean4_basic.py
@@ -0,0 +1,164 @@
+"""
+Tests for Lean 4 Language Server integration with Serena.
+
+Tests prove that Serena's symbol tools can:
+1. Start the Lean 4 language server
+2. Discover all expected symbols with precise matching
+3. Track within-file references
+4. Track cross-file references
+
+Test Repository Structure:
+- Helper.lean: Calculator structure, arithmetic functions (add, subtract), predicates (isPositive, absolute)
+- Main.lean: Main entry point using Helper, plus multiply and calculate functions
+"""
+
+import pytest
+
+from solidlsp.ls import SolidLanguageServer
+from solidlsp.ls_config import Language
+
+
+@pytest.mark.lean4
+class TestLean4LanguageServer:
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_ls_is_running(self, language_server: SolidLanguageServer) -> None:
+ """Test that the Lean 4 language server starts successfully."""
+ assert language_server.is_running()
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_helper_symbols(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test symbol discovery in Helper.lean.
+
+ Verifies that Serena can identify:
+ - Structure definition (Calculator)
+ - All functions (add, subtract, isPositive, absolute)
+ """
+ all_symbols, _ = language_server.request_document_symbols("Helper.lean").get_all_symbols_and_roots()
+ symbol_names = {s["name"] for s in all_symbols}
+
+ expected_symbols = {
+ "Calculator",
+ "add",
+ "subtract",
+ "isPositive",
+ "absolute",
+ }
+
+ missing = expected_symbols - symbol_names
+ assert not missing, f"Missing expected symbols in Helper.lean: {missing}"
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_main_symbols(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test symbol discovery in Main.lean.
+
+ Verifies that Serena can identify locally defined functions.
+ """
+ all_symbols, _ = language_server.request_document_symbols("Main.lean").get_all_symbols_and_roots()
+ symbol_names = {s["name"] for s in all_symbols}
+
+ expected_symbols = {
+ "multiply",
+ "calculate",
+ "main",
+ }
+
+ missing = expected_symbols - symbol_names
+ assert not missing, f"Missing expected symbols in Main.lean: {missing}"
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_within_file_references(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test within-file reference tracking for isPositive.
+
+ isPositive is defined in Helper.lean line 11 (0-indexed) and used by absolute on line 15.
+ """
+ # isPositive defined at line 11, column 4
+ references = language_server.request_references("Helper.lean", line=11, column=4)
+
+ assert len(references) >= 1, f"Expected at least 1 reference to isPositive (used in absolute), got {len(references)}"
+
+ # Check that isPositive is referenced within Helper.lean at line 15 (absolute calls isPositive)
+ ref_locations = [(ref["relativePath"], ref["range"]["start"]["line"]) for ref in references]
+ helper_refs = [(path, line) for path, line in ref_locations if "Helper.lean" in path]
+ assert any(line == 15 for _, line in helper_refs), (
+ f"Expected isPositive reference at Helper.lean:15 (in absolute), got: {ref_locations}"
+ )
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_cross_file_references_add(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test cross-file reference tracking for add function.
+
+ add is defined in Helper.lean line 5 (0-indexed) and used in Main.lean on lines 7 and 15.
+ """
+ # add defined at line 5, column 4
+ references = language_server.request_references("Helper.lean", line=5, column=4)
+
+ assert len(references) >= 1, f"Expected at least 1 reference to add in Main.lean, got {len(references)}"
+
+ # Check for references in Main.lean with specific lines
+ ref_locations = [(ref["relativePath"], ref["range"]["start"]["line"]) for ref in references]
+ main_refs = [(path, line) for path, line in ref_locations if "Main.lean" in path]
+ assert len(main_refs) >= 1, f"Expected at least 1 reference to add in Main.lean, got: {ref_locations}"
+ main_ref_lines = {line for _, line in main_refs}
+ # add is used in Main.lean line 7 (in calculate) and line 15 (in main)
+ assert 7 in main_ref_lines or 15 in main_ref_lines, (
+ f"Expected add references at Main.lean lines 7 or 15, got lines: {main_ref_lines}"
+ )
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_cross_file_references_calculator(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test cross-file reference tracking for Calculator structure.
+
+ Calculator is defined in Helper.lean line 0 (0-indexed) and used in Main.lean lines 5 and 13.
+ """
+ # Calculator defined at line 0, column 10
+ references = language_server.request_references("Helper.lean", line=0, column=10)
+
+ assert len(references) >= 1, f"Expected at least 1 reference to Calculator in Main.lean, got {len(references)}"
+
+ ref_locations = [(ref["relativePath"], ref["range"]["start"]["line"]) for ref in references]
+ main_refs = [(path, line) for path, line in ref_locations if "Main.lean" in path]
+ assert len(main_refs) >= 1, f"Expected at least 1 reference to Calculator in Main.lean, got: {ref_locations}"
+ main_ref_lines = {line for _, line in main_refs}
+ # Calculator is used in Main.lean line 5 (calculate signature) and line 13 (let c : Calculator)
+ assert 5 in main_ref_lines or 13 in main_ref_lines, (
+ f"Expected Calculator references at Main.lean lines 5 or 13, got lines: {main_ref_lines}"
+ )
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_go_to_definition_within_file(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test go-to-definition within a file.
+
+ In Main.lean line 19, calculate is called: 'match calculate c "multiply" 6 7 with'.
+ calculate is defined at Main.lean line 5.
+ """
+ # calculate usage in Main.lean line 19, 'calculate' starts at col 8
+ definitions = language_server.request_definition("Main.lean", line=19, column=8)
+
+ assert len(definitions) >= 1, f"Expected at least 1 definition for calculate, got {len(definitions)}"
+
+ def_location = definitions[0]
+ assert def_location["uri"].endswith("Main.lean"), f"Expected definition in Main.lean, got: {def_location['uri']}"
+ assert def_location["range"]["start"]["line"] == 5, f"Expected definition at line 5, got: {def_location['range']['start']['line']}"
+
+ @pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
+ def test_go_to_definition_across_files(self, language_server: SolidLanguageServer) -> None:
+ """
+ Test go-to-definition across files.
+
+ In Main.lean line 15, add is called: 'add 5 3'.
+ add is defined in Helper.lean line 5.
+ """
+ # add usage in Main.lean line 15, 'add' starts at col 19
+ definitions = language_server.request_definition("Main.lean", line=15, column=19)
+
+ assert len(definitions) >= 1, f"Expected at least 1 definition for add, got {len(definitions)}"
+
+ def_location = definitions[0]
+ assert def_location["uri"].endswith("Helper.lean"), f"Expected definition in Helper.lean, got: {def_location['uri']}"
+ assert def_location["range"]["start"]["line"] == 5, f"Expected definition at line 5, got: {def_location['range']['start']['line']}"
diff --git a/test/solidlsp/lua/test_lua_basic.py b/test/solidlsp/lua/test_lua_basic.py
index 633e3c45..ab58ec5e 100644
--- a/test/solidlsp/lua/test_lua_basic.py
+++ b/test/solidlsp/lua/test_lua_basic.py
@@ -146,15 +146,15 @@ class TestLuaLanguageServer:
# The declaration may or may not be included
if "calculator.lua" in ref_files:
- assert (
- 5 in ref_files["calculator.lua"]
- ), f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['calculator.lua']}"
+ assert 5 in ref_files["calculator.lua"], (
+ f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['calculator.lua']}"
+ )
# Check main.lua has usages
assert "main.lua" in ref_files, "Should find add usages in main.lua"
- assert (
- 15 in ref_files["main.lua"] or 70 in ref_files["main.lua"]
- ), f"Should find add usage in main.lua, found at lines {ref_files.get('main.lua', [])}"
+ assert 15 in ref_files["main.lua"] or 70 in ref_files["main.lua"], (
+ f"Should find add usage in main.lua, found at lines {ref_files.get('main.lua', [])}"
+ )
# Check for cross-file references from main.lua
main_refs = [ref for ref in refs if "main.lua" in ref.get("uri", "")]
@@ -202,15 +202,15 @@ class TestLuaLanguageServer:
# The declaration may or may not be included
if "utils.lua" in ref_files:
- assert (
- 5 in ref_files["utils.lua"]
- ), f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['utils.lua']}"
+ assert 5 in ref_files["utils.lua"], (
+ f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['utils.lua']}"
+ )
# Check main.lua has usage
assert "main.lua" in ref_files, "Should find trim usage in main.lua"
- assert (
- 31 in ref_files["main.lua"]
- ), f"Should find trim usage at line 32 (0-indexed: 31) in main.lua, found at lines {ref_files.get('main.lua', [])}"
+ assert 31 in ref_files["main.lua"], (
+ f"Should find trim usage at line 32 (0-indexed: 31) in main.lua, found at lines {ref_files.get('main.lua', [])}"
+ )
# Check for cross-file references from main.lua
main_refs = [ref for ref in refs if "main.lua" in ref.get("uri", "")]
diff --git a/test/solidlsp/luau/__init__.py b/test/solidlsp/luau/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/solidlsp/luau/test_luau_basic.py b/test/solidlsp/luau/test_luau_basic.py
new file mode 100644
index 00000000..264ace92
--- /dev/null
+++ b/test/solidlsp/luau/test_luau_basic.py
@@ -0,0 +1,153 @@
+"""
+Tests for the Luau language server implementation.
+
+These tests validate symbol finding, within-file references,
+and cross-file reference capabilities for Luau modules and functions.
+"""
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.ls_config import Language
+
+
+@pytest.mark.luau
+class TestLuauLanguageServer:
+ """Test Luau language server symbol finding and cross-file references."""
+
+ @pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
+ def test_find_symbols_in_init(self, language_server: SolidLanguageServer) -> None:
+ """Test finding specific functions in init.luau."""
+ symbols = language_server.request_document_symbols("src/init.luau").get_all_symbols_and_roots()
+
+ assert symbols is not None
+ assert len(symbols) > 0
+
+ symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
+ symbol_names = set()
+ for symbol in symbol_list:
+ if isinstance(symbol, dict):
+ name = symbol.get("name", "")
+ symbol_names.add(name)
+
+ assert "createConfig" in symbol_names, f"createConfig not found in symbols: {symbol_names}"
+ assert "main" in symbol_names, f"main not found in symbols: {symbol_names}"
+
+ @pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
+ def test_find_symbols_in_module(self, language_server: SolidLanguageServer) -> None:
+ """Test finding specific functions in module.luau."""
+ symbols = language_server.request_document_symbols("src/module.luau").get_all_symbols_and_roots()
+
+ assert symbols is not None
+ assert len(symbols) > 0
+
+ symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
+ symbol_names = set()
+ for symbol in symbol_list:
+ if isinstance(symbol, dict):
+ name = symbol.get("name", "")
+ symbol_names.add(name)
+
+ assert "process" in symbol_names, f"process not found in symbols: {symbol_names}"
+ assert "helper" in symbol_names, f"helper not found in symbols: {symbol_names}"
+
+ @pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
+ def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
+ """Test finding within-file references to createConfig in init.luau.
+
+ createConfig is defined at line 8 (0-indexed) and referenced at lines 17 and 23.
+ """
+ symbols = language_server.request_document_symbols("src/init.luau").get_all_symbols_and_roots()
+
+ assert symbols is not None
+ symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
+
+ # Find the createConfig function symbol
+ create_config_symbol = None
+ for sym in symbol_list:
+ if isinstance(sym, dict) and sym.get("name") == "createConfig":
+ create_config_symbol = sym
+ break
+
+ assert create_config_symbol is not None, "createConfig function not found in init.luau"
+
+ range_info = create_config_symbol.get("selectionRange", create_config_symbol.get("range"))
+ assert range_info is not None, "createConfig has no range information"
+
+ range_start = range_info["start"]
+ refs = language_server.request_references("src/init.luau", range_start["line"], range_start["character"])
+
+ assert refs is not None
+ assert isinstance(refs, list)
+ # createConfig appears multiple times within init.luau:
+ # definition (line 8), usage in main (line 17), and return table (line 23)
+ assert len(refs) >= 2, f"Should find at least 2 references to createConfig, found {len(refs)}"
+
+ # Verify that references are in init.luau
+ ref_files = set()
+ for ref in refs:
+ filename = ref.get("uri", "").split("/")[-1]
+ ref_files.add(filename)
+
+ assert "init.luau" in ref_files, f"Expected references in init.luau, found in: {ref_files}"
+
+ @pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
+ def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
+ """Test finding cross-file references to process function.
+
+ process is defined in module.luau and used in init.luau via module.process().
+ """
+ symbols = language_server.request_document_symbols("src/module.luau").get_all_symbols_and_roots()
+
+ assert symbols is not None
+ symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
+
+ # Find the process function symbol
+ process_symbol = None
+ for sym in symbol_list:
+ if isinstance(sym, dict) and sym.get("name") == "process":
+ process_symbol = sym
+ break
+
+ assert process_symbol is not None, "process function not found in module.luau"
+
+ range_info = process_symbol.get("selectionRange", process_symbol.get("range"))
+ assert range_info is not None, "process function has no range information"
+
+ range_start = range_info["start"]
+ refs = language_server.request_references("src/module.luau", range_start["line"], range_start["character"])
+
+ assert refs is not None
+ assert isinstance(refs, list)
+ assert len(refs) >= 1, f"Should find at least 1 reference to process, found {len(refs)}"
+
+ # Collect reference files and lines
+ ref_info: dict[str, list[int]] = {}
+ for ref in refs:
+ filename = ref.get("uri", "").split("/")[-1]
+ if filename not in ref_info:
+ ref_info[filename] = []
+ ref_info[filename].append(ref["range"]["start"]["line"])
+
+ # The definition in module.luau may or may not be included
+ # We expect at least the reference in module.luau return table (line 9)
+ assert "module.luau" in ref_info, f"Expected references in module.luau, found in: {set(ref_info.keys())}"
+
+ @pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
+ def test_find_definition(self, language_server: SolidLanguageServer) -> None:
+ """Test finding definition of createConfig from its usage in main().
+
+ createConfig is used at line 17, column 20 (0-indexed) in init.luau.
+ Its definition should be at line 8 in init.luau.
+ """
+ # Line 17 (0-indexed): ` local config = createConfig("test", 42)`
+ # createConfig starts at column 20
+ definition_locations = language_server.request_definition("src/init.luau", 17, 20)
+
+ assert definition_locations, f"Expected non-empty definition list but got {definition_locations}"
+ assert len(definition_locations) >= 1
+
+ definition = definition_locations[0]
+ assert definition["uri"].endswith("init.luau"), f"Definition should be in init.luau, got: {definition['uri']}"
+ # createConfig is defined at line 8 (0-indexed): `local function createConfig(...)`
+ assert definition["range"]["start"]["line"] == 8, f"Definition should be at line 8, got line {definition['range']['start']['line']}"
diff --git a/test/solidlsp/luau/test_luau_dependency_provider.py b/test/solidlsp/luau/test_luau_dependency_provider.py
new file mode 100644
index 00000000..35ad9de3
--- /dev/null
+++ b/test/solidlsp/luau/test_luau_dependency_provider.py
@@ -0,0 +1,164 @@
+"""Tests for the Luau language server dependency provider."""
+
+import io
+import zipfile
+from pathlib import Path
+from unittest.mock import patch
+
+import pytest
+
+from solidlsp.language_servers.luau_lsp import LuauLanguageServer
+from solidlsp.settings import SolidLSPSettings
+
+
+def _make_provider(
+ tmp_path: Path,
+ custom_settings: dict[str, str] | None = None,
+) -> LuauLanguageServer.DependencyProvider:
+ return LuauLanguageServer.DependencyProvider(
+ custom_settings=SolidLSPSettings.CustomLSSettings(custom_settings or {}),
+ ls_resources_dir=str(tmp_path),
+ )
+
+
+class _FakeResponse:
+ def __init__(self, content: bytes) -> None:
+ self.content = content
+
+ def raise_for_status(self) -> None:
+ return
+
+ def iter_content(self, chunk_size: int = 8192):
+ yield self.content
+
+ def __enter__(self) -> "_FakeResponse":
+ return self
+
+ def __exit__(self, exc_type, exc, tb) -> None:
+ return None
+
+
+@pytest.mark.luau
+class TestLuauDependencyProvider:
+ def test_create_launch_command_uses_ls_path_override_and_adds_assets(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path, {"ls_path": "/custom/luau-lsp"})
+
+ with patch.object(
+ provider,
+ "_get_or_install_core_dependency",
+ side_effect=AssertionError("_get_or_install_core_dependency should not be called when ls_path is set"),
+ ):
+ with patch.object(
+ provider,
+ "_resolve_support_files",
+ return_value=("/tmp/globalTypes.d.luau", "/tmp/en-us.json"),
+ ):
+ assert provider.create_launch_command() == [
+ "/custom/luau-lsp",
+ "lsp",
+ "--definitions:@roblox=/tmp/globalTypes.d.luau",
+ "--docs=/tmp/en-us.json",
+ ]
+
+ def test_resolve_support_files_defaults_to_roblox_mode(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path)
+
+ with patch.object(
+ provider,
+ "_download_roblox_support_files",
+ return_value=("/tmp/globalTypes.PluginSecurity.d.luau", "/tmp/en-us.json"),
+ ) as download_roblox_support_files:
+ with patch.object(
+ provider,
+ "_download_standard_docs",
+ side_effect=AssertionError("_download_standard_docs should not be called in roblox mode"),
+ ):
+ assert provider._resolve_support_files() == (
+ "/tmp/globalTypes.PluginSecurity.d.luau",
+ "/tmp/en-us.json",
+ )
+
+ download_roblox_support_files.assert_called_once_with("PluginSecurity")
+
+ def test_resolve_support_files_uses_standard_mode_docs_only(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path, {"platform": "standard"})
+
+ with patch.object(provider, "_download_standard_docs", return_value="/tmp/luau-en-us.json") as download_standard_docs:
+ with patch.object(
+ provider,
+ "_download_roblox_support_files",
+ side_effect=AssertionError("_download_roblox_support_files should not be called in standard mode"),
+ ):
+ assert provider._resolve_support_files() == (
+ None,
+ "/tmp/luau-en-us.json",
+ )
+
+ download_standard_docs.assert_called_once_with()
+
+ def test_get_or_install_core_dependency_uses_system_binary(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path)
+
+ with patch("solidlsp.language_servers.luau_lsp.shutil.which", return_value="/usr/bin/luau-lsp"):
+ with patch.object(
+ provider,
+ "_download_luau_lsp",
+ side_effect=AssertionError("_download_luau_lsp should not be called when luau-lsp is on PATH"),
+ ):
+ assert provider._get_or_install_core_dependency() == "/usr/bin/luau-lsp"
+
+ def test_download_luau_lsp_extracts_binary_into_ls_resources_dir(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path)
+
+ archive = io.BytesIO()
+ with zipfile.ZipFile(archive, "w") as zip_file:
+ zip_file.writestr("nested/luau-lsp", "#!/bin/sh\n")
+
+ with patch("solidlsp.language_servers.luau_lsp.platform.system", return_value="Linux"):
+ with patch("solidlsp.language_servers.luau_lsp.platform.machine", return_value="aarch64"):
+ with patch("solidlsp.language_servers.luau_lsp.requests.get", return_value=_FakeResponse(archive.getvalue())):
+ binary_path = provider._download_luau_lsp()
+
+ resolved_binary = Path(binary_path)
+ assert resolved_binary.exists()
+ assert resolved_binary.name == "luau-lsp"
+ assert str(resolved_binary).startswith(str(tmp_path))
+
+ def test_download_roblox_support_files_writes_into_ls_resources_dir(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path)
+
+ with patch(
+ "solidlsp.language_servers.luau_lsp.requests.get",
+ side_effect=[_FakeResponse(b"types"), _FakeResponse(b"docs")],
+ ):
+ definitions_path, docs_path = provider._download_roblox_support_files("LocalUserSecurity")
+
+ assert definitions_path == str(tmp_path / "globalTypes.LocalUserSecurity.d.luau")
+ assert docs_path == str(tmp_path / "en-us.json")
+ assert (tmp_path / "globalTypes.LocalUserSecurity.d.luau").read_bytes() == b"types"
+ assert (tmp_path / "en-us.json").read_bytes() == b"docs"
+
+ def test_download_standard_docs_writes_into_ls_resources_dir(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path, {"platform": "standard"})
+
+ with patch("solidlsp.language_servers.luau_lsp.requests.get", return_value=_FakeResponse(b"docs")):
+ docs_path = provider._download_standard_docs()
+
+ assert docs_path == str(tmp_path / "luau-en-us.json")
+ assert (tmp_path / "luau-en-us.json").read_bytes() == b"docs"
+
+ def test_workspace_configuration_uses_configured_platform(self) -> None:
+ config = LuauLanguageServer._get_workspace_configuration(SolidLSPSettings.CustomLSSettings({"platform": "standard"}))
+ assert config == {"platform": {"type": "standard"}}
+
+ def test_invalid_platform_raises(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path, {"platform": "invalid"})
+
+ with pytest.raises(ValueError, match="Unsupported Luau platform"):
+ provider._resolve_support_files()
+
+ def test_invalid_roblox_security_level_raises(self, tmp_path: Path) -> None:
+ provider = _make_provider(tmp_path, {"roblox_security_level": "invalid"})
+
+ with pytest.raises(ValueError, match="Unsupported Luau Roblox security level"):
+ provider._resolve_support_files()
diff --git a/test/solidlsp/markdown/test_markdown_basic.py b/test/solidlsp/markdown/test_markdown_basic.py
index e8f96c5a..5a3c65f8 100644
--- a/test/solidlsp/markdown/test_markdown_basic.py
+++ b/test/solidlsp/markdown/test_markdown_basic.py
@@ -35,9 +35,9 @@ class TestMarkdownLanguageServerBasics:
# Verify that markdown headings are remapped from String to Namespace
for symbol in all_symbols:
- assert (
- symbol["kind"] == SymbolKind.Namespace
- ), f"Heading '{symbol['name']}' should have kind Namespace, got {SymbolKind(symbol['kind']).name}"
+ assert symbol["kind"] == SymbolKind.Namespace, (
+ f"Heading '{symbol['name']}' should have kind Namespace, got {SymbolKind(symbol['kind']).name}"
+ )
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
def test_markdown_request_symbols_from_guide(self, language_server: SolidLanguageServer) -> None:
@@ -79,9 +79,9 @@ class TestMarkdownLanguageServerBasics:
for symbol in all_symbols:
ls_symbol = LanguageServerSymbol(symbol)
- assert (
- not ls_symbol.is_low_level()
- ), f"Heading '{symbol['name']}' should not be low-level (kind={SymbolKind(symbol['kind']).name})"
+ assert not ls_symbol.is_low_level(), (
+ f"Heading '{symbol['name']}' should not be low-level (kind={SymbolKind(symbol['kind']).name})"
+ )
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
def test_markdown_nested_headings_remapped(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/nix/test_nix_basic.py b/test/solidlsp/nix/test_nix_basic.py
index 92024104..42d4d414 100644
--- a/test/solidlsp/nix/test_nix_basic.py
+++ b/test/solidlsp/nix/test_nix_basic.py
@@ -114,7 +114,7 @@ class TestNixLanguageServer:
if refs:
ref_lines = sorted([ref["range"]["start"]["line"] for ref in refs])
# Check if we found the inherit (line 67, 0-indexed: 66)
- assert 66 in ref_lines, f"Should find makeGreeting inherit at line 67, found at lines {[l+1 for l in ref_lines]}"
+ assert 66 in ref_lines, f"Should find makeGreeting inherit at line 67, found at lines {[l + 1 for l in ref_lines]}"
@pytest.mark.xfail(is_ci, reason="Test is flaky") # TODO: Re-enable if the hover test becomes more stable (#1040)
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
@@ -148,9 +148,9 @@ class TestNixLanguageServer:
if default_refs:
ref_lines = sorted([ref["range"]["start"]["line"] for ref in default_refs])
# Check for key references - at least the import (line 10) or usage (line 24)
- assert (
- 9 in ref_lines or 23 in ref_lines
- ), f"Should find utils import or usage, found references at lines {[l+1 for l in ref_lines]}"
+ assert 9 in ref_lines or 23 in ref_lines, (
+ f"Should find utils import or usage, found references at lines {[l + 1 for l in ref_lines]}"
+ )
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_verify_imports_exist(self, language_server: SolidLanguageServer) -> None:
@@ -187,9 +187,9 @@ class TestNixLanguageServer:
if len(definitions) > 0:
# Should point to the import statement or utils.nix
- assert any(
- "utils" in def_item.get("uri", "") or "default.nix" in def_item.get("uri", "") for def_item in definitions
- ), "Definition should relate to utils import or utils.nix file"
+ assert any("utils" in def_item.get("uri", "") or "default.nix" in def_item.get("uri", "") for def_item in definitions), (
+ "Definition should relate to utils import or utils.nix file"
+ )
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_definition_navigation_in_flake(self, language_server: SolidLanguageServer) -> None:
@@ -202,9 +202,9 @@ class TestNixLanguageServer:
assert isinstance(definitions, list)
# nixd should find the definition of hello-custom in the same file
if len(definitions) > 0:
- assert any(
- "flake.nix" in def_item.get("uri", "") for def_item in definitions
- ), "Should find hello-custom definition in flake.nix"
+ assert any("flake.nix" in def_item.get("uri", "") for def_item in definitions), (
+ "Should find hello-custom definition in flake.nix"
+ )
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/ocaml/test_cross_file_refs.py b/test/solidlsp/ocaml/test_cross_file_refs.py
new file mode 100644
index 00000000..0a2b70bc
--- /dev/null
+++ b/test/solidlsp/ocaml/test_cross_file_refs.py
@@ -0,0 +1,75 @@
+"""
+Test cross-file references for OCaml.
+
+Cross-file references require OCaml >= 5.2 and ocaml-lsp-server >= 1.23.0.
+On environments without these (e.g. Windows CI with OCaml 4.14), only
+same-file references are asserted.
+"""
+
+import logging
+import os
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.language_servers.ocaml_lsp_server import OcamlLanguageServer
+from solidlsp.ls_config import Language
+
+log = logging.getLogger(__name__)
+
+
+@pytest.mark.ocaml
+class TestCrossFileReferences:
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_fib_has_cross_file_references(self, language_server: SolidLanguageServer) -> None:
+ """Test that fib function references are found across multiple files.
+
+ The `fib` function is defined in lib/test_repo.ml and used in:
+ - lib/test_repo.ml (definition + 2 recursive calls)
+ - bin/main.ml (1 call)
+ - test/test_test_repo.ml (5 references)
+
+ Total: 9 references across 3 files.
+ """
+ file_path = os.path.join("lib", "test_repo.ml")
+
+ fib_line = 7
+ fib_char = 8
+
+ refs = language_server.request_references(file_path, fib_line, fib_char)
+
+ lib_refs = [ref for ref in refs if "lib/test_repo.ml" in ref.get("uri", "")]
+ bin_refs = [ref for ref in refs if "bin/main.ml" in ref.get("uri", "")]
+ test_refs = [ref for ref in refs if "test/test_test_repo.ml" in ref.get("uri", "")]
+
+ log.info("Cross-file references result:")
+ log.info(f"Total references found: {len(refs)}")
+ log.info(f" lib/test_repo.ml: {len(lib_refs)}")
+ log.info(f" bin/main.ml: {len(bin_refs)}")
+ log.info(f" test/test_test_repo.ml: {len(test_refs)}")
+
+ for ref in refs:
+ uri = ref.get("uri", "")
+ filename = uri.split("/")[-1]
+ line = ref.get("range", {}).get("start", {}).get("line", -1)
+ log.info(f" {filename}:{line}")
+
+ # Same-file references always work
+ assert len(lib_refs) >= 3, f"Expected at least 3 references in lib/test_repo.ml (definition + 2 recursive), but got {len(lib_refs)}"
+
+ # Cross-file references require OCaml >= 5.2 and ocaml-lsp-server >= 1.23.0
+ if isinstance(language_server, OcamlLanguageServer) and language_server.supports_cross_file_references:
+ assert len(refs) >= 9, (
+ f"Expected at least 9 total references (3 in lib + 1 in bin + 5 in test), "
+ f"but got {len(refs)}. Cross-file references are NOT working!"
+ )
+
+ assert len(bin_refs) >= 1, (
+ f"Expected at least 1 reference in bin/main.ml, but got {len(bin_refs)}. "
+ "Cross-file references are NOT working - bin/main.ml not found!"
+ )
+
+ assert len(test_refs) >= 1, (
+ f"Expected at least 1 reference in test/test_test_repo.ml, but got {len(test_refs)}. "
+ "Cross-file references are NOT working - test file not found!"
+ )
diff --git a/test/solidlsp/ocaml/test_ocaml_basic.py b/test/solidlsp/ocaml/test_ocaml_basic.py
new file mode 100644
index 00000000..82af5fa9
--- /dev/null
+++ b/test/solidlsp/ocaml/test_ocaml_basic.py
@@ -0,0 +1,152 @@
+import os
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.ls_config import Language
+from solidlsp.ls_utils import SymbolUtils
+
+
+@pytest.mark.ocaml
+class TestOCamlLanguageServer:
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
+ symbols = language_server.request_full_symbol_tree()
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoModule"), "DemoModule not found in symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "fib"), "fib not found in symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "someFunction"), "someFunction function not found in symbol tree"
+
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
+ file_path = os.path.join("lib", "test_repo.ml")
+
+ # Use the correct character position for 'fib' function name
+ # Line 8: "let rec fib n =" - 'fib' starts at character 8 (0-indexed)
+ fib_line = 7 # 0-indexed line number
+ fib_char = 8 # 0-indexed character position
+
+ refs = language_server.request_references(file_path, fib_line, fib_char)
+
+ # Should find at least 3 references: definition + 2 recursive calls in same file
+ assert len(refs) >= 3, f"Expected at least 3 references to fib (definition + 2 recursive), found {len(refs)}"
+
+ # All references should be in lib/test_repo.ml (same file as definition)
+ # Use forward slashes for URI matching (URIs always use /)
+ lib_refs = [ref for ref in refs if "lib/test_repo.ml" in ref.get("uri", "")]
+ assert len(lib_refs) >= 3, f"Expected at least 3 references in lib/test_repo.ml, found {len(lib_refs)}"
+
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_mixed_ocaml_modules(self, language_server: SolidLanguageServer) -> None:
+ """Test that the language server can find symbols from OCaml modules"""
+ # Test that full symbol tree includes symbols from various file types
+ all_symbols = language_server.request_full_symbol_tree()
+
+ # Should find symbols from main OCaml files
+ assert SymbolUtils.symbol_tree_contains_name(all_symbols, "fib"), "Should find fib from .ml file"
+ assert SymbolUtils.symbol_tree_contains_name(all_symbols, "DemoModule"), "Should find DemoModule from .ml file"
+ assert SymbolUtils.symbol_tree_contains_name(all_symbols, "someFunction"), "Should find someFunction from DemoModule"
+ assert SymbolUtils.symbol_tree_contains_name(all_symbols, "num_domains"), "Should find num_domains constant"
+
+ def test_reason_file_patterns(self) -> None:
+ """Test that OCaml language configuration recognizes Reason file extensions"""
+ from solidlsp.ls_config import Language
+
+ ocaml_lang = Language.OCAML
+ file_matcher = ocaml_lang.get_source_fn_matcher()
+
+ # Test OCaml extensions
+ assert file_matcher.is_relevant_filename("test.ml"), "Should match .ml files"
+ assert file_matcher.is_relevant_filename("test.mli"), "Should match .mli files"
+
+ # Test Reason extensions
+ assert file_matcher.is_relevant_filename("test.re"), "Should match .re files"
+ assert file_matcher.is_relevant_filename("test.rei"), "Should match .rei files"
+
+ # Test non-matching extensions
+ assert not file_matcher.is_relevant_filename("test.py"), "Should not match .py files"
+ assert not file_matcher.is_relevant_filename("test.js"), "Should not match .js files"
+
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_module_hierarchy_navigation(self, language_server: SolidLanguageServer) -> None:
+ """Test navigation within module hierarchy including DemoModule."""
+ file_path = os.path.join("lib", "test_repo.ml")
+
+ # Use correct position for 'DemoModule' (line 1, char 7)
+ # Line 1: "module DemoModule = struct" - 'DemoModule' starts around char 7
+ module_line = 0 # 0-indexed
+ module_char = 7 # 0-indexed
+
+ refs = language_server.request_references(file_path, module_line, module_char)
+
+ # Should find at least 1 reference (the definition)
+ assert len(refs) >= 1, f"Expected at least 1 reference to DemoModule, found {len(refs)}"
+
+ # Check that references are found - use forward slashes for URI matching
+ lib_refs = [ref for ref in refs if "lib/test_repo.ml" in ref.get("uri", "")]
+ assert len(lib_refs) >= 1, f"Expected at least 1 reference in lib/test_repo.ml, found {len(lib_refs)}"
+
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_let_binding_references(self, language_server: SolidLanguageServer) -> None:
+ """Test finding references to let-bound values across files."""
+ file_path = os.path.join("lib", "test_repo.ml")
+
+ # Use correct position for 'num_domains' (line 12, char 4)
+ # Line 12: "let num_domains = 2" - 'num_domains' starts around char 4
+ num_domains_line = 11 # 0-indexed
+ num_domains_char = 4 # 0-indexed
+
+ refs = language_server.request_references(file_path, num_domains_line, num_domains_char)
+
+ # Should find at least 1 reference (the definition)
+ assert len(refs) >= 1, f"Expected at least 1 reference to num_domains, found {len(refs)}"
+
+ # Check that reference is found in the definition file - use forward slashes
+ ml_refs = [ref for ref in refs if "lib/test_repo.ml" in ref.get("uri", "")]
+ assert len(ml_refs) >= 1, f"Expected at least 1 reference in lib/test_repo.ml, found {len(ml_refs)}"
+
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_recursive_function_analysis(self, language_server: SolidLanguageServer) -> None:
+ """Test that recursive function calls are properly identified within the definition file."""
+ file_path = os.path.join("lib", "test_repo.ml")
+
+ # Use correct position for 'fib' function name (line 8, char 8)
+ fib_line = 7 # 0-indexed
+ fib_char = 8 # 0-indexed
+
+ refs = language_server.request_references(file_path, fib_line, fib_char)
+
+ # Filter to references within the definition file only - use forward slashes
+ same_file_refs = [ref for ref in refs if "lib/test_repo.ml" in ref.get("uri", "")]
+
+ # Should find at least 3 references in test_repo.ml: definition + 2 recursive calls
+ # On OCaml 5.2+ with cross-file refs, there may be more total refs but same-file count stays the same
+ assert len(same_file_refs) >= 3, (
+ f"Expected at least 3 references in test_repo.ml (definition + 2 recursive), found {len(same_file_refs)}"
+ )
+
+ # Verify references are on different lines (definition + recursive calls)
+ ref_lines = [ref.get("range", {}).get("start", {}).get("line", -1) for ref in same_file_refs]
+ unique_lines = len(set(ref_lines))
+ assert unique_lines >= 2, f"Recursive calls should appear on multiple lines, found {unique_lines} unique lines"
+
+ @pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
+ def test_open_statement_resolution(self, language_server: SolidLanguageServer) -> None:
+ """Test that open statements allow unqualified access to module contents."""
+ # In bin/main.ml, fib is called without Test_repo prefix due to 'open Test_repo'
+ all_symbols = language_server.request_full_symbol_tree()
+
+ # Should be able to find fib through symbol tree
+ fib_accessible = SymbolUtils.symbol_tree_contains_name(all_symbols, "fib")
+ assert fib_accessible, "fib should be accessible through open statement"
+
+ # DemoModule should also be accessible
+ demo_module_accessible = SymbolUtils.symbol_tree_contains_name(all_symbols, "DemoModule")
+ assert demo_module_accessible, "DemoModule should be accessible"
+
+ # Verify we have access to both qualified and unqualified symbols
+ assert len(all_symbols) > 0, "Should find symbols from OCaml files"
+
+ # Test that the language server recognizes the open statement context
+ file_path = os.path.join("bin", "main.ml")
+ symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
+ assert len(symbols) > 0, "Should find symbols in main.ml that use opened modules"
diff --git a/test/solidlsp/php/test_php_basic.py b/test/solidlsp/php/test_php_basic.py
index eea00e83..bc59d471 100644
--- a/test/solidlsp/php/test_php_basic.py
+++ b/test/solidlsp/php/test_php_basic.py
@@ -184,3 +184,75 @@ class TestPhpLanguageServers:
usage_in_index_php = {"uri_suffix": "index.php", "line": 13, "character": 0}
assert usage_in_index_php in actual_locations_comparable, "Usage of helperFunction in index.php not found"
+
+ @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
+ def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
+ """Test that document symbols are properly retrieved after Intelephense capability fix."""
+ from solidlsp.ls_utils import SymbolUtils
+
+ symbols = language_server.request_full_symbol_tree()
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "helperFunction"), "helperFunction not found in symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet function not found in symbol tree"
+
+ @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
+ def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
+ """Test that document symbols are properly retrieved for a specific file."""
+ doc_symbols = language_server.request_document_symbols("helper.php")
+ all_symbols = doc_symbols.get_all_symbols_and_roots()
+ symbol_names = [sym.get("name") for sym in all_symbols[0] if sym.get("name")]
+ assert "helperFunction" in symbol_names, f"helperFunction not found in document symbols. Found: {symbol_names}"
+
+ @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
+ def test_document_symbols_hierarchical_structure(self, language_server: SolidLanguageServer) -> None:
+ """Verify Intelephense returns hierarchical DocumentSymbol format.
+
+ When hierarchicalDocumentSymbolSupport is declared in client capabilities,
+ Intelephense returns DocumentSymbol[] where class methods appear as children
+ of their parent class. Without this declaration, it falls back to a flat
+ SymbolInformation[] list where all symbols appear at root level with no
+ parent-child relationships.
+ """
+ all_symbols, root_symbols = language_server.request_document_symbols("sample.php").get_all_symbols_and_roots()
+
+ root_names = [s.get("name") for s in root_symbols]
+ assert "Animal" in root_names, f"Animal class not found at root level. Roots: {root_names}"
+ assert "Dog" in root_names, f"Dog class not found at root level. Roots: {root_names}"
+ assert "Cat" in root_names, f"Cat class not found at root level. Roots: {root_names}"
+
+ # Verify Dog has method children — this is the key assertion for hierarchical support.
+ # With a flat response, Dog would have no children and all methods would be at root level.
+ dog_symbol = next((s for s in root_symbols if s.get("name") == "Dog"), None)
+ assert dog_symbol is not None, "Dog class not found in root symbols"
+ dog_children = dog_symbol.get("children", [])
+ dog_child_names = [c.get("name") for c in dog_children]
+ assert len(dog_child_names) > 0, (
+ f"Dog class has no children — hierarchicalDocumentSymbolSupport is not working. All root symbols: {root_names}"
+ )
+ expected_methods = {"greet", "fetch", "getBreed", "describe"}
+ missing = expected_methods - set(dog_child_names)
+ assert not missing, f"Dog class missing expected methods: {missing}. Children found: {dog_child_names}"
+
+ # Methods must NOT appear at root level (that would indicate the flat fallback format).
+ assert "greet" not in root_names, f"greet should be a child of Dog, not at root level. Roots: {root_names}"
+ assert "fetch" not in root_names, f"fetch should be a child of Dog, not at root level. Roots: {root_names}"
+
+ @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
+ def test_full_symbol_tree_within_file(self, language_server: SolidLanguageServer) -> None:
+ """Verify request_full_symbol_tree scoped to a PHP file returns correct symbols.
+
+ This validates that Intelephense responds correctly when symbols are requested
+ for a single file, including class/method hierarchy in sample.php.
+ """
+ from solidlsp.ls_utils import SymbolUtils
+
+ symbols = language_server.request_full_symbol_tree(within_relative_path="sample.php")
+
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "Dog"), "Dog not found in sample.php symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "Animal"), "Animal not found in sample.php symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet method not found in sample.php symbol tree"
+ assert SymbolUtils.symbol_tree_contains_name(symbols, "fetch"), "fetch method not found in sample.php symbol tree"
+
+ # Methods must appear as children of Dog, not as root-level symbols
+ dog_root = next((s for s in symbols if s.get("name") == "Dog"), None)
+ if dog_root is not None:
+ assert SymbolUtils.symbol_tree_contains_name([dog_root], "greet"), "greet should be nested under Dog in symbol tree"
diff --git a/test/solidlsp/powershell/test_powershell_basic.py b/test/solidlsp/powershell/test_powershell_basic.py
index 0d8a7349..3d6ef7a1 100644
--- a/test/solidlsp/powershell/test_powershell_basic.py
+++ b/test/solidlsp/powershell/test_powershell_basic.py
@@ -149,9 +149,9 @@ class TestPowerShellLanguageServerBasics:
# Should find at least the call site in Main function
assert refs is not None and len(refs) >= 1, f"Should find references to Greet-User, got {refs}"
- assert any(
- "main.ps1" in ref.get("uri", ref.get("relativePath", "")) for ref in refs
- ), f"Should find reference in main.ps1, got {refs}"
+ assert any("main.ps1" in ref.get("uri", ref.get("relativePath", "")) for ref in refs), (
+ f"Should find reference in main.ps1, got {refs}"
+ )
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
def test_powershell_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
@@ -166,9 +166,9 @@ class TestPowerShellLanguageServerBasics:
definition_locations = language_server.request_definition(main_path, 98, 18)
# Should find the definition in utils.ps1
- assert (
- definition_locations is not None and len(definition_locations) >= 1
- ), f"Should find definition of Convert-ToUpperCase, got {definition_locations}"
- assert any(
- "utils.ps1" in loc.get("uri", "") for loc in definition_locations
- ), f"Should find definition in utils.ps1, got {definition_locations}"
+ assert definition_locations is not None and len(definition_locations) >= 1, (
+ f"Should find definition of Convert-ToUpperCase, got {definition_locations}"
+ )
+ assert any("utils.ps1" in loc.get("uri", "") for loc in definition_locations), (
+ f"Should find definition in utils.ps1, got {definition_locations}"
+ )
diff --git a/test/solidlsp/r/test_r_basic.py b/test/solidlsp/r/test_r_basic.py
index 3ce63a0b..647ff6c1 100644
--- a/test/solidlsp/r/test_r_basic.py
+++ b/test/solidlsp/r/test_r_basic.py
@@ -76,9 +76,9 @@ class TestRLanguageServer:
assert len(utils_refs) >= 1, "Should find at least one reference in utils.R"
utils_ref = utils_refs[0]
# Should be around line 6 where calculate_mean is defined (0-indexed: line 5)
- assert (
- utils_ref["range"]["start"]["line"] == 5
- ), f"Expected reference at line 5 in utils.R, got line {utils_ref['range']['start']['line']}"
+ assert utils_ref["range"]["start"]["line"] == 5, (
+ f"Expected reference at line 5 in utils.R, got line {utils_ref['range']['start']['line']}"
+ )
def test_file_matching(self):
"""Test that R files are properly matched."""
diff --git a/test/solidlsp/rego/test_rego_basic.py b/test/solidlsp/rego/test_rego_basic.py
index e4185e85..79155a0c 100644
--- a/test/solidlsp/rego/test_rego_basic.py
+++ b/test/solidlsp/rego/test_rego_basic.py
@@ -1,7 +1,6 @@
"""Tests for Rego language server (Regal) functionality."""
import os
-import sys
import pytest
@@ -11,9 +10,6 @@ from solidlsp.ls_utils import SymbolUtils
@pytest.mark.rego
-@pytest.mark.skipif(
- sys.platform == "win32", reason="Regal LSP has Windows path handling bug - see https://github.com/StyraInc/regal/issues/1683"
-)
class TestRegoLanguageServer:
"""Test Regal language server functionality for Rego."""
@@ -115,9 +111,9 @@ class TestRegoLanguageServer:
assert definitions is not None and len(definitions) > 0, "Should find cross-file definition for is_valid_user"
# Verify the definition points to helpers.rego (cross-file)
- assert any(
- "helpers.rego" in defn.get("relativePath", "") for defn in definitions
- ), "Definition should be in utils/helpers.rego (cross-file reference)"
+ assert any("helpers.rego" in defn.get("relativePath", "") for defn in definitions), (
+ "Definition should be in utils/helpers.rego (cross-file reference)"
+ )
@pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
def test_find_symbols_validation(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/ruby/test_ruby_symbol_retrieval.py b/test/solidlsp/ruby/test_ruby_symbol_retrieval.py
index 55efcf02..5d969094 100644
--- a/test/solidlsp/ruby/test_ruby_symbol_retrieval.py
+++ b/test/solidlsp/ruby/test_ruby_symbol_retrieval.py
@@ -33,9 +33,9 @@ class TestRubyLanguageServerSymbols:
# Verify that we found the containing symbol
assert containing_symbol is not None, "Should find containing symbol for method position"
assert containing_symbol["name"] == "create_user", f"Expected 'create_user', got '{containing_symbol['name']}'"
- assert (
- containing_symbol["kind"] == SymbolKind.Method.value
- ), f"Expected Method kind ({SymbolKind.Method.value}), got {containing_symbol['kind']}"
+ assert containing_symbol["kind"] == SymbolKind.Method.value, (
+ f"Expected Method kind ({SymbolKind.Method.value}), got {containing_symbol['kind']}"
+ )
# Verify location information
assert "location" in containing_symbol, "Containing symbol should have location information"
@@ -68,9 +68,9 @@ class TestRubyLanguageServerSymbols:
# Verify that we found the containing symbol
assert containing_symbol is not None, "Should find containing symbol for class position"
assert containing_symbol["name"] == "UserService", f"Expected 'UserService', got '{containing_symbol['name']}'"
- assert (
- containing_symbol["kind"] == SymbolKind.Class.value
- ), f"Expected Class kind ({SymbolKind.Class.value}), got {containing_symbol['kind']}"
+ assert containing_symbol["kind"] == SymbolKind.Class.value, (
+ f"Expected Class kind ({SymbolKind.Class.value}), got {containing_symbol['kind']}"
+ )
# Verify location information exists
assert "location" in containing_symbol, "Class symbol should have location information"
@@ -80,9 +80,9 @@ class TestRubyLanguageServerSymbols:
# Verify the class is properly nested in the Services module
if "containerName" in containing_symbol:
- assert (
- containing_symbol["containerName"] == "Services"
- ), f"Expected 'Services' as container, got '{containing_symbol['containerName']}'"
+ assert containing_symbol["containerName"] == "Services", (
+ f"Expected 'Services' as container, got '{containing_symbol['containerName']}'"
+ )
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None:
@@ -411,9 +411,9 @@ class TestRubyLanguageServerSymbols:
except Exception as e:
# Some language servers throw exceptions for invalid positions, which is acceptable
- assert (
- "symbol" in str(e).lower() or "position" in str(e).lower() or "reference" in str(e).lower()
- ), f"Exception should be related to symbol/position/reference issues, got: {e}"
+ assert "symbol" in str(e).lower() or "position" in str(e).lower() or "reference" in str(e).lower(), (
+ f"Exception should be related to symbol/position/reference issues, got: {e}"
+ )
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
@@ -492,9 +492,9 @@ class TestRubyLanguageServerSymbols:
assert "kind" in containing_symbol, "Containing symbol should have a kind"
# The containing symbol should be a method, class, or similar construct
expected_kinds = [SymbolKind.Method, SymbolKind.Class, SymbolKind.Function, SymbolKind.Constructor]
- assert containing_symbol["kind"] in [
- k.value for k in expected_kinds
- ], f"Expected containing symbol to be method/class/function, got kind: {containing_symbol['kind']}"
+ assert containing_symbol["kind"] in [k.value for k in expected_kinds], (
+ f"Expected containing symbol to be method/class/function, got kind: {containing_symbol['kind']}"
+ )
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
@@ -553,9 +553,9 @@ class TestRubyLanguageServerSymbols:
child_names = [c.get("name") for c in child["children"]]
expected_names = ["UserStats", "UserManager", "process_user_data"]
found_expected = [name for name in expected_names if name in child_names]
- assert (
- len(found_expected) > 0
- ), f"Should find symbols in user_management, expected {expected_names}, found {child_names}"
+ assert len(found_expected) > 0, (
+ f"Should find symbols in user_management, expected {expected_names}, found {child_names}"
+ )
break
if not found_user_management:
diff --git a/test/solidlsp/rust/test_rust_analyzer_detection.py b/test/solidlsp/rust/test_rust_analyzer_detection.py
index 85a978c3..fea2bb0e 100644
--- a/test/solidlsp/rust/test_rust_analyzer_detection.py
+++ b/test/solidlsp/rust/test_rust_analyzer_detection.py
@@ -33,38 +33,104 @@ class TestRustAnalyzerDetection:
"""Unit tests for rust-analyzer binary detection logic."""
@pytest.mark.rust
- def test_detect_from_path_as_last_resort(self):
+ def test_detect_from_path_when_functional(self):
"""
GIVEN rustup is not available
- AND rust-analyzer is NOT in common locations (Homebrew, cargo)
- AND rust-analyzer IS in system PATH
+ AND rust-analyzer IS in system PATH and is functional
WHEN _ensure_rust_analyzer_installed is called
- THEN it should return the path from shutil.which as last resort
+ THEN it should return the path from shutil.which
- WHY: PATH is checked last to avoid picking up incorrect aliases.
- Users with rust-analyzer in PATH but not via rustup/common locations
- should still work.
+ WHY: PATH is checked after rustup but before common locations.
+ This covers standalone installs via Nix, distro packages, etc.
+ The binary is verified to be functional to avoid broken rustup proxy symlinks.
+ Fixes: https://github.com/oraios/serena/issues/800
"""
from solidlsp.language_servers.rust_analyzer import RustAnalyzer
# Mock rustup to be unavailable
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
- # Mock common locations to NOT exist
- with patch("os.path.isfile", return_value=False):
+ with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None):
# Mock PATH to have rust-analyzer
with patch("shutil.which") as mock_which:
mock_which.return_value = "/custom/bin/rust-analyzer"
- with patch("os.access", return_value=True):
- # Need isfile to return True for PATH result only
- def selective_isfile(path):
- return path == "/custom/bin/rust-analyzer"
-
- with patch("os.path.isfile", side_effect=selective_isfile):
- result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
+ with patch("os.path.isfile", return_value=True):
+ with patch("os.access", return_value=True):
+ with patch.object(RustAnalyzer.DependencyProvider, "_is_rust_analyzer_functional", return_value=True):
+ result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
assert result == "/custom/bin/rust-analyzer"
mock_which.assert_called_with("rust-analyzer")
+ @pytest.mark.rust
+ def test_detect_from_nix_path_without_rustup(self):
+ """
+ GIVEN rustup is NOT installed
+ AND rust-analyzer is installed via Nix and available in PATH
+ WHEN _ensure_rust_analyzer_installed is called
+ THEN it should find and use the Nix-provided rust-analyzer
+
+ WHY: NixOS users install rust-analyzer via home-manager or nix-env,
+ which places it in ~/.nix-profile/bin/ on PATH. Without rustup,
+ Serena should still detect and use this binary.
+ Fixes: https://github.com/oraios/serena/issues/800
+ """
+ from solidlsp.language_servers.rust_analyzer import RustAnalyzer
+
+ nix_path = "/home/user/.nix-profile/bin/rust-analyzer"
+
+ with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
+ with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None):
+ with patch("shutil.which", return_value=nix_path):
+ with patch("os.path.isfile", return_value=True):
+ with patch("os.access", return_value=True):
+ with patch.object(RustAnalyzer.DependencyProvider, "_is_rust_analyzer_functional", return_value=True):
+ result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
+
+ assert result == nix_path
+
+ @pytest.mark.rust
+ def test_skip_broken_rustup_proxy_in_path(self):
+ """
+ GIVEN rustup is NOT available via 'rustup which'
+ AND rust-analyzer in PATH is a broken rustup proxy symlink
+ WHEN _ensure_rust_analyzer_installed is called
+ THEN it should skip the broken proxy and fall through to common paths
+
+ WHY: On some systems, rust-analyzer in PATH is a symlink to rustup.
+ If rustup doesn't have the component installed, running the binary fails.
+ We verify functionality with --version before accepting a PATH result.
+ """
+ from solidlsp.language_servers.rust_analyzer import RustAnalyzer
+
+ # Use platform-appropriate paths and binary names
+ home = pathlib.Path.home()
+ if IS_WINDOWS:
+ binary_name = "rust-analyzer.exe"
+ broken_proxy_path = str(home / "AppData" / "Local" / "Microsoft" / "WindowsApps" / binary_name)
+ cargo_path = str(home / ".cargo" / "bin" / binary_name)
+ else:
+ binary_name = "rust-analyzer"
+ broken_proxy_path = "/usr/bin/rust-analyzer"
+ cargo_path = os.path.expanduser("~/.cargo/bin/rust-analyzer")
+
+ def mock_isfile(path):
+ return path in [broken_proxy_path, cargo_path]
+
+ def mock_access(path, mode):
+ return path in [broken_proxy_path, cargo_path]
+
+ with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
+ with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None):
+ with patch("shutil.which", return_value=broken_proxy_path):
+ with patch("os.path.isfile", side_effect=mock_isfile):
+ with patch("os.access", side_effect=mock_access):
+ # The PATH binary is a broken rustup proxy
+ with patch.object(RustAnalyzer.DependencyProvider, "_is_rust_analyzer_functional", return_value=False):
+ result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
+
+ # Should skip broken proxy and find cargo-installed version
+ assert result == cargo_path
+
@pytest.mark.rust
@pytest.mark.skipif(IS_WINDOWS, reason="Homebrew paths only apply to macOS/Linux")
def test_detect_from_homebrew_apple_silicon_path(self):
diff --git a/test/solidlsp/rust/test_rust_basic.py b/test/solidlsp/rust/test_rust_basic.py
index c827d4b2..8d84d736 100644
--- a/test/solidlsp/rust/test_rust_basic.py
+++ b/test/solidlsp/rust/test_rust_basic.py
@@ -22,9 +22,9 @@ class TestRustLanguageServer:
assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
sel_start = add_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
- assert any(
- "main.rs" in ref.get("relativePath", "") for ref in refs
- ), "main.rs should reference add (raw, tried all positions in selectionRange)"
+ assert any("main.rs" in ref.get("relativePath", "") for ref in refs), (
+ "main.rs should reference add (raw, tried all positions in selectionRange)"
+ )
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
@@ -46,9 +46,9 @@ class TestRustLanguageServer:
assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
sel_start = add_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
- assert any(
- "main.rs" in ref.get("relativePath", "") for ref in refs
- ), "main.rs should reference add (tried all positions in selectionRange)"
+ assert any("main.rs" in ref.get("relativePath", "") for ref in refs), (
+ "main.rs should reference add (tried all positions in selectionRange)"
+ )
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
diff --git a/test/solidlsp/solidity/__init__.py b/test/solidlsp/solidity/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/solidlsp/solidity/test_solidity_basic.py b/test/solidlsp/solidity/test_solidity_basic.py
new file mode 100644
index 00000000..348c460b
--- /dev/null
+++ b/test/solidlsp/solidity/test_solidity_basic.py
@@ -0,0 +1,165 @@
+"""
+Basic integration tests for the Solidity language server.
+
+Tests validate symbol detection and reference finding using the Solidity test repository,
+which contains a simple ERC-20 Token contract, a SafeMath library, and an IERC20 interface.
+"""
+
+import re
+from pathlib import Path
+from typing import Optional
+
+import pytest
+
+from solidlsp import SolidLanguageServer
+from solidlsp.ls_config import Language
+
+
+def _find_identifier_position(file_path: Path, symbol_name: str) -> Optional[tuple[int, int]]:
+ """Return the (line, column) of the first occurrence of *symbol_name* as an identifier.
+
+ Scans the file for a word-boundary match of *symbol_name* so that the position
+ returned is the exact location of the identifier, regardless of what range the
+ language server reports for the surrounding symbol. Returns None if not found.
+ """
+ pattern = re.compile(r"\b" + re.escape(symbol_name) + r"\b")
+ with file_path.open(encoding="utf-8") as fh:
+ for line_idx, line in enumerate(fh):
+ m = pattern.search(line)
+ if m:
+ return line_idx, m.start()
+ return None
+
+
+@pytest.mark.solidity
+class TestSolidityLanguageServerBasics:
+ """Test basic functionality of the Solidity language server."""
+
+ @pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.SOLIDITY], indirect=True)
+ def test_solidity_language_server_initialization(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Test that the Solidity language server starts and initializes correctly."""
+ assert language_server is not None
+ assert language_server.language == Language.SOLIDITY
+ assert language_server.is_running()
+ assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
+
+ @pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.SOLIDITY], indirect=True)
+ def test_token_contract_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Test that document symbols are found in Token.sol.
+
+ Verifies contract, state variables, errors, events, and function symbols.
+ """
+ all_symbols, root_symbols = language_server.request_document_symbols("contracts/Token.sol").get_all_symbols_and_roots()
+
+ assert all_symbols is not None, "Should return symbols for Token.sol"
+ assert len(all_symbols) > 0, f"Should find symbols in Token.sol, found {len(all_symbols)}"
+
+ symbol_names = [sym.get("name") for sym in all_symbols]
+
+ # Contract-level symbol
+ assert "Token" in symbol_names, "Should detect the Token contract"
+
+ # State variables
+ assert "name" in symbol_names, "Should detect the 'name' state variable"
+ assert "symbol" in symbol_names, "Should detect the 'symbol' state variable"
+ assert "decimals" in symbol_names, "Should detect the 'decimals' state variable"
+
+ # Custom errors
+ assert "ZeroAddress" in symbol_names, "Should detect the 'ZeroAddress' custom error"
+ assert "InsufficientBalance" in symbol_names, "Should detect the 'InsufficientBalance' custom error"
+
+ # Functions
+ assert "totalSupply" in symbol_names, "Should detect the 'totalSupply' function"
+ assert "balanceOf" in symbol_names, "Should detect the 'balanceOf' function"
+ assert "transfer" in symbol_names, "Should detect the 'transfer' function"
+ assert "approve" in symbol_names, "Should detect the 'approve' function"
+ assert "transferFrom" in symbol_names, "Should detect the 'transferFrom' function"
+
+ @pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.SOLIDITY], indirect=True)
+ def test_interface_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Test that document symbols are found in IERC20.sol."""
+ all_symbols, root_symbols = language_server.request_document_symbols("contracts/interfaces/IERC20.sol").get_all_symbols_and_roots()
+
+ assert all_symbols is not None, "Should return symbols for IERC20.sol"
+ assert len(all_symbols) > 0, f"Should find symbols in IERC20.sol, found {len(all_symbols)}"
+
+ symbol_names = [sym.get("name") for sym in all_symbols]
+
+ # Interface
+ assert "IERC20" in symbol_names, "Should detect the IERC20 interface"
+
+ # Events
+ assert "Transfer" in symbol_names, "Should detect the Transfer event"
+ assert "Approval" in symbol_names, "Should detect the Approval event"
+
+ # View functions
+ assert "totalSupply" in symbol_names, "Should detect totalSupply"
+ assert "balanceOf" in symbol_names, "Should detect balanceOf"
+ assert "allowance" in symbol_names, "Should detect allowance"
+
+ # Mutating functions
+ assert "transfer" in symbol_names, "Should detect transfer"
+ assert "approve" in symbol_names, "Should detect approve"
+ assert "transferFrom" in symbol_names, "Should detect transferFrom"
+
+ @pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.SOLIDITY], indirect=True)
+ def test_library_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Test that document symbols are found in SafeMath.sol."""
+ all_symbols, root_symbols = language_server.request_document_symbols("contracts/lib/SafeMath.sol").get_all_symbols_and_roots()
+
+ assert all_symbols is not None, "Should return symbols for SafeMath.sol"
+ assert len(all_symbols) > 0, f"Should find symbols in SafeMath.sol, found {len(all_symbols)}"
+
+ symbol_names = [sym.get("name") for sym in all_symbols]
+
+ # Library
+ assert "SafeMath" in symbol_names, "Should detect the SafeMath library"
+
+ # Library functions
+ assert "add" in symbol_names, "Should detect the 'add' function"
+ assert "sub" in symbol_names, "Should detect the 'sub' function"
+ assert "mul" in symbol_names, "Should detect the 'mul' function"
+ assert "div" in symbol_names, "Should detect the 'div' function"
+
+ @pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.SOLIDITY], indirect=True)
+ def test_within_file_references(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Test finding within-file references to the _transfer helper in Token.sol."""
+ # Use the file to find the exact identifier position: the Solidity LSP reports
+ # the symbol range starting at the preceding whitespace/comment block, not the
+ # function keyword, so we locate '_transfer' directly in the source.
+ pos = _find_identifier_position(repo_path / "contracts/Token.sol", "_transfer")
+ assert pos is not None, "Should find '_transfer' identifier in Token.sol"
+ definition_line, definition_char = pos
+
+ references = language_server.request_references("contracts/Token.sol", definition_line, definition_char)
+
+ assert references is not None, "Should return references for '_transfer'"
+ assert len(references) >= 2, (
+ f"'_transfer' should have at least 2 references (callers), found {len(references)}"
+ ) # called in transfer() and transferFrom()
+
+ ref_files = {ref.get("uri", "") for ref in references}
+ assert any("Token.sol" in uri for uri in ref_files), "References should include Token.sol"
+
+ @pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
+ @pytest.mark.parametrize("repo_path", [Language.SOLIDITY], indirect=True)
+ def test_cross_file_references(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
+ """Test finding cross-file references: IERC20.transfer implemented in Token.sol."""
+ # Use 'transfer' in the interface — Token.sol inherits IERC20 and overrides it,
+ # so the LSP resolves the implementation site in Token.sol as a cross-file reference.
+ pos = _find_identifier_position(repo_path / "contracts/interfaces/IERC20.sol", "transfer")
+ assert pos is not None, "Should find 'transfer' identifier in IERC20.sol"
+ definition_line, definition_char = pos
+
+ references = language_server.request_references("contracts/interfaces/IERC20.sol", definition_line, definition_char)
+
+ assert references is not None, "Should return cross-file references for IERC20.transfer"
+ assert len(references) >= 1, f"IERC20.transfer should be referenced at least once (in Token.sol), found {len(references)}"
+
+ ref_files = {ref.get("uri", "") for ref in references}
+ assert any("Token.sol" in uri for uri in ref_files), "IERC20.transfer references should include Token.sol"
diff --git a/test/solidlsp/systemverilog/test_systemverilog_basic.py b/test/solidlsp/systemverilog/test_systemverilog_basic.py
index edd72d2f..b3a8a4c5 100644
--- a/test/solidlsp/systemverilog/test_systemverilog_basic.py
+++ b/test/solidlsp/systemverilog/test_systemverilog_basic.py
@@ -69,9 +69,9 @@ class TestSystemVerilogDefinition:
assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
def_in_counter = [d for d in definitions if "counter.sv" in (d.get("relativePath") or "")]
assert len(def_in_counter) >= 1, f"Expected definition in counter.sv, got: {[d.get('relativePath') for d in definitions]}"
- assert (
- def_in_counter[0]["range"]["start"]["line"] == 7
- ), f"Expected definition at line 7 (output port count), got line {def_in_counter[0]['range']['start']['line']}"
+ assert def_in_counter[0]["range"]["start"]["line"] == 7, (
+ f"Expected definition at line 7 (output port count), got line {def_in_counter[0]['range']['start']['line']}"
+ )
@pytest.mark.parametrize("language_server", [Language.SYSTEMVERILOG], indirect=True)
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
@@ -87,9 +87,9 @@ class TestSystemVerilogDefinition:
def_paths = [d.get("relativePath", "") for d in definitions]
assert any("counter.sv" in p for p in def_paths), f"Expected definition in counter.sv, got: {def_paths}"
counter_defs = [d for d in definitions if "counter.sv" in (d.get("relativePath") or "")]
- assert (
- counter_defs[0]["range"]["start"]["line"] == 1
- ), f"Expected definition at line 1 (module counter), got line {counter_defs[0]['range']['start']['line']}"
+ assert counter_defs[0]["range"]["start"]["line"] == 1, (
+ f"Expected definition at line 1 (module counter), got line {counter_defs[0]['range']['start']['line']}"
+ )
@pytest.mark.systemverilog
@@ -129,9 +129,9 @@ class TestSystemVerilogReferences:
assert any("top.sv" in p for p in ref_paths), f"Expected reference from top.sv, got: {ref_paths}"
refs_in_top = [r for r in references if "top.sv" in (r.get("relativePath") or "")]
# top.sv line 17 (0-indexed: 16): " counter #(.WIDTH(8)) u_counter ("
- assert (
- refs_in_top[0]["range"]["start"]["line"] == 16
- ), f"Expected cross-file reference at line 16 (counter instantiation), got line {refs_in_top[0]['range']['start']['line']}"
+ assert refs_in_top[0]["range"]["start"]["line"] == 16, (
+ f"Expected cross-file reference at line 16 (counter instantiation), got line {refs_in_top[0]['range']['start']['line']}"
+ )
def _extract_hover_text(hover_info: dict[str, Any]) -> str:
@@ -256,9 +256,9 @@ class TestSystemVerilogRename:
edit_lines = sorted(e["range"]["start"]["line"] for e in edits)
assert 1 in edit_lines, f"Expected edit at line 1 (module declaration), got lines: {edit_lines}"
decl_edits = [e for e in edits if e["range"]["start"]["line"] == 1]
- assert (
- decl_edits[0]["range"]["start"]["character"] == 7
- ), f"Expected edit at char 7, got char {decl_edits[0]['range']['start']['character']}"
+ assert decl_edits[0]["range"]["start"]["character"] == 7, (
+ f"Expected edit at char 7, got char {decl_edits[0]['range']['start']['character']}"
+ )
for uri, file_edits in changes.items():
for edit in file_edits:
assert edit["newText"] == "my_counter", f"Expected 'my_counter', got {edit['newText']}"
diff --git a/test/solidlsp/toml/test_toml_symbol_retrieval.py b/test/solidlsp/toml/test_toml_symbol_retrieval.py
index 9458c51a..5538a4c8 100644
--- a/test/solidlsp/toml/test_toml_symbol_retrieval.py
+++ b/test/solidlsp/toml/test_toml_symbol_retrieval.py
@@ -85,9 +85,9 @@ class TestTomlSymbolRetrieval:
# Children should include TOML files
child_names = {child["name"] for child in root.get("children", [])}
# Note: File names are stripped of extension in some cases
- assert (
- "Cargo" in child_names or "Cargo.toml" in child_names or any("cargo" in name.lower() for name in child_names)
- ), f"Should find Cargo.toml in tree, got: {child_names}"
+ assert "Cargo" in child_names or "Cargo.toml" in child_names or any("cargo" in name.lower() for name in child_names), (
+ f"Should find Cargo.toml in tree, got: {child_names}"
+ )
@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
diff --git a/test/solidlsp/typescript/test_typescript_basic.py b/test/solidlsp/typescript/test_typescript_basic.py
index 9570bdc2..58c560f0 100644
--- a/test/solidlsp/typescript/test_typescript_basic.py
+++ b/test/solidlsp/typescript/test_typescript_basic.py
@@ -28,6 +28,6 @@ class TestTypescriptLanguageServer:
assert helper_symbol is not None, "Could not find 'helperFunction' symbol in index.ts"
sel_start = helper_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
- assert any(
- "index.ts" in ref.get("relativePath", "") for ref in refs
- ), "index.ts should reference helperFunction (tried all positions in selectionRange)"
+ assert any("index.ts" in ref.get("relativePath", "") for ref in refs), (
+ "index.ts should reference helperFunction (tried all positions in selectionRange)"
+ )
diff --git a/test/solidlsp/vue/test_vue_basic.py b/test/solidlsp/vue/test_vue_basic.py
index c46f35aa..ca6a5e19 100644
--- a/test/solidlsp/vue/test_vue_basic.py
+++ b/test/solidlsp/vue/test_vue_basic.py
@@ -85,9 +85,9 @@ class TestVueDualLspArchitecture:
# Verify references include .vue files (components that import the store)
vue_refs = [ref for ref in refs if ".vue" in ref.get("uri", "")]
- assert (
- len(vue_refs) >= 3
- ), f"Should find at least 3 references in Vue components, found {len(vue_refs)}: {[ref.get('uri', '') for ref in vue_refs]}"
+ assert len(vue_refs) >= 3, (
+ f"Should find at least 3 references in Vue components, found {len(vue_refs)}: {[ref.get('uri', '') for ref in vue_refs]}"
+ )
# Verify specific components that use the store
expected_vue_files = ["App.vue", "CalculatorInput.vue", "CalculatorDisplay.vue"]
@@ -131,9 +131,9 @@ class TestVueDualLspArchitecture:
all_ref_uris = [ref.get("uri", "") for ref in refs]
has_ts_refs = any(".ts" in uri and "types" not in uri for uri in all_ref_uris)
- assert (
- has_ts_refs
- ), f"Operation type should be referenced in TypeScript files like calculator.ts. Found references in: {all_ref_uris}"
+ assert has_ts_refs, (
+ f"Operation type should be referenced in TypeScript files like calculator.ts. Found references in: {all_ref_uris}"
+ )
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_reference_deduplication(self, language_server: SolidLanguageServer) -> None:
@@ -260,9 +260,9 @@ class TestVueEdgeCases:
# Vue LSP returns SFC structure (template/script/style sections) for .vue files
# This is expected behavior - overview shows the file's high-level structure
- assert (
- len(symbol_names) >= 1
- ), f"Should have at least 1 symbol name in overview (e.g., 'App' or SFC section), got {len(symbol_names)}: {symbol_names}"
+ assert len(symbol_names) >= 1, (
+ f"Should have at least 1 symbol name in overview (e.g., 'App' or SFC section), got {len(symbol_names)}: {symbol_names}"
+ )
# Test overview for a TypeScript file
store_file = os.path.join("src", "stores", "calculator.ts")
@@ -272,18 +272,18 @@ class TestVueEdgeCases:
assert len(store_overview) >= 1, f"calculator.ts should have at least 1 top-level symbol in overview, got {len(store_overview)}"
store_symbol_names = [s.get("name") for s in store_overview if isinstance(s, dict)]
- assert (
- "useCalculatorStore" in store_symbol_names
- ), f"useCalculatorStore should be in store file overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
+ assert "useCalculatorStore" in store_symbol_names, (
+ f"useCalculatorStore should be in store file overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
+ )
# Test overview for another Vue component
button_file = os.path.join("src", "components", "CalculatorButton.vue")
button_overview = language_server.request_document_overview(button_file)
assert isinstance(button_overview, list), f"Button overview should be a list, got: {type(button_overview)}"
- assert (
- len(button_overview) >= 1
- ), f"CalculatorButton.vue should have at least 1 top-level symbol in overview, got {len(button_overview)}"
+ assert len(button_overview) >= 1, (
+ f"CalculatorButton.vue should have at least 1 top-level symbol in overview, got {len(button_overview)}"
+ )
# For Vue files, overview provides SFC structure which is useful for navigation
# The detailed symbols are available via request_document_symbols
@@ -324,9 +324,9 @@ class TestVueEdgeCases:
stores_overview = language_server.request_dir_overview(stores_dir)
assert isinstance(stores_overview, dict), f"Stores overview should be a dict, got: {type(stores_overview)}"
- assert (
- len(stores_overview) == 1
- ), f"src/stores directory should have exactly 1 file (calculator.ts) in overview, got {len(stores_overview)}"
+ assert len(stores_overview) == 1, (
+ f"src/stores directory should have exactly 1 file (calculator.ts) in overview, got {len(stores_overview)}"
+ )
# Verify calculator.ts is included
calculator_files = [path for path in stores_overview.keys() if "calculator.ts" in path]
@@ -339,18 +339,18 @@ class TestVueEdgeCases:
store_path = calculator_files[0]
store_symbols = stores_overview[store_path]
store_symbol_names = [s.get("name") for s in store_symbols if isinstance(s, dict)]
- assert (
- "useCalculatorStore" in store_symbol_names
- ), f"calculator.ts should have useCalculatorStore in overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
+ assert "useCalculatorStore" in store_symbol_names, (
+ f"calculator.ts should have useCalculatorStore in overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
+ )
# Test overview for composables directory
composables_dir = os.path.join("src", "composables")
composables_overview = language_server.request_dir_overview(composables_dir)
assert isinstance(composables_overview, dict), f"Composables overview should be a dict, got: {type(composables_overview)}"
- assert (
- len(composables_overview) == 2
- ), f"src/composables directory should have exactly 2 files in overview, got {len(composables_overview)}"
+ assert len(composables_overview) == 2, (
+ f"src/composables directory should have exactly 2 files in overview, got {len(composables_overview)}"
+ )
# Verify composable files are included
expected_composables = ["useFormatter.ts", "useTheme.ts"]
diff --git a/test/solidlsp/vue/test_vue_error_cases.py b/test/solidlsp/vue/test_vue_error_cases.py
index 5396666f..ee6a5496 100644
--- a/test/solidlsp/vue/test_vue_error_cases.py
+++ b/test/solidlsp/vue/test_vue_error_cases.py
@@ -235,9 +235,9 @@ class TestVueUndefinedSymbols:
result = language_server.request_containing_symbol(file_path, 1, 0)
# Should handle gracefully - return None, empty dict, or a valid parent symbol
- assert (
- result is None or result == {} or isinstance(result, dict)
- ), f"Whitespace line should return None, empty dict, or valid symbol. Got: {result}"
+ assert result is None or result == {} or isinstance(result, dict), (
+ f"Whitespace line should return None, empty dict, or valid symbol. Got: {result}"
+ )
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_definition_at_keyword_position(self, language_server: SolidLanguageServer) -> None:
@@ -271,9 +271,9 @@ class TestVueEdgeCasePositions:
result = language_server.request_containing_symbol(file_path, 0, 0)
# Should handle gracefully
- assert (
- result is None or result == {} or isinstance(result, dict)
- ), f"Position 0,0 should return None, empty dict, or valid symbol. Got: {result}"
+ assert result is None or result == {} or isinstance(result, dict), (
+ f"Position 0,0 should return None, empty dict, or valid symbol. Got: {result}"
+ )
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_references_at_file_start(self, language_server: SolidLanguageServer) -> None:
@@ -317,9 +317,9 @@ class TestVueEdgeCasePositions:
result = language_server.request_containing_symbol(file_path, 5, 10)
# Should handle gracefully - template doesn't have containing symbols in the same way
- assert (
- result is None or result == {} or isinstance(result, dict)
- ), f"Template position should return None, empty dict, or valid symbol. Got: {result}"
+ assert result is None or result == {} or isinstance(result, dict), (
+ f"Template position should return None, empty dict, or valid symbol. Got: {result}"
+ )
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_zero_character_positions(self, language_server: SolidLanguageServer) -> None:
@@ -334,9 +334,9 @@ class TestVueEdgeCasePositions:
result = language_server.request_containing_symbol(file_path, line, 0)
# Should handle gracefully
- assert (
- result is None or result == {} or isinstance(result, dict)
- ), f"Line {line}, character 0 should return None, empty dict, or valid symbol. Got: {result}"
+ assert result is None or result == {} or isinstance(result, dict), (
+ f"Line {line}, character 0 should return None, empty dict, or valid symbol. Got: {result}"
+ )
class TestVueTypescriptFileErrors:
diff --git a/test/solidlsp/vue/test_vue_rename.py b/test/solidlsp/vue/test_vue_rename.py
index 5ba99fd9..01ffbe25 100644
--- a/test/solidlsp/vue/test_vue_rename.py
+++ b/test/solidlsp/vue/test_vue_rename.py
@@ -121,9 +121,9 @@ class TestVueRename:
assert len(document_changes) > 0, "Should have at least one document change"
composable_changes = [dc for dc in document_changes if "useFormatter.ts" in dc.get("textDocument", {}).get("uri", "")]
- assert (
- len(composable_changes) > 0
- ), f"Should have edits for useFormatter.ts (definition). Found changes for: {[dc.get('textDocument', {}).get('uri', '') for dc in document_changes]}"
+ assert len(composable_changes) > 0, (
+ f"Should have edits for useFormatter.ts (definition). Found changes for: {[dc.get('textDocument', {}).get('uri', '') for dc in document_changes]}"
+ )
for change in document_changes:
assert "textDocument" in change, "Document change should have textDocument"
diff --git a/test/solidlsp/vue/test_vue_symbol_retrieval.py b/test/solidlsp/vue/test_vue_symbol_retrieval.py
index 7d33f88f..04a22e70 100644
--- a/test/solidlsp/vue/test_vue_symbol_retrieval.py
+++ b/test/solidlsp/vue/test_vue_symbol_retrieval.py
@@ -93,9 +93,9 @@ class TestVueSymbolRetrieval:
containing_symbol = language_server.request_containing_symbol(file_path, import_line, import_character)
# Should return None or empty dictionary for positions without containing symbol
- assert (
- containing_symbol is None or containing_symbol == {}
- ), f"Expected None or empty dict for import position, got {containing_symbol}"
+ assert containing_symbol is None or containing_symbol == {}, (
+ f"Expected None or empty dict for import position, got {containing_symbol}"
+ )
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_request_referencing_symbols_store_function(self, language_server: SolidLanguageServer) -> None:
@@ -153,9 +153,9 @@ class TestVueSymbolRetrieval:
]
# Verify we found references - useFormatter is imported and used in CalculatorInput.vue
- assert (
- len(ref_symbols) >= 1
- ), f"useFormatter should have at least 1 reference (used in CalculatorInput.vue), found {len(ref_symbols)} references"
+ assert len(ref_symbols) >= 1, (
+ f"useFormatter should have at least 1 reference (used in CalculatorInput.vue), found {len(ref_symbols)} references"
+ )
# Check for references in Vue components
vue_refs = [
@@ -182,9 +182,9 @@ class TestVueSymbolRetrieval:
definitions = language_server.request_definition(input_file, 4, 10)
assert len(definitions) == 1, f"Should find exactly 1 definition for CalculatorButton import, got {len(definitions)}"
- assert (
- "CalculatorButton.vue" in definitions[0]["relativePath"]
- ), f"Definition should point to CalculatorButton.vue, got {definitions[0]['relativePath']}"
+ assert "CalculatorButton.vue" in definitions[0]["relativePath"], (
+ f"Definition should point to CalculatorButton.vue, got {definitions[0]['relativePath']}"
+ )
refs = language_server.request_references(input_file, 4, 10)
@@ -225,9 +225,9 @@ class TestVueSymbolRetrieval:
# Verify it points to the store file
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
- assert (
- "calculator.ts" in defining_symbol["location"]["uri"]
- ), f"Should point to calculator.ts, got {defining_symbol['location']['uri']}"
+ assert "calculator.ts" in defining_symbol["location"]["uri"], (
+ f"Should point to calculator.ts, got {defining_symbol['location']['uri']}"
+ )
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_request_defining_symbol_component_import(self, language_server: SolidLanguageServer) -> None:
@@ -239,9 +239,9 @@ class TestVueSymbolRetrieval:
definition = definitions[0]
assert definition["relativePath"] is not None, "Definition should have a relative path"
- assert (
- "CalculatorButton.vue" in definition["relativePath"]
- ), f"Should point to CalculatorButton.vue, got {definition['relativePath']}"
+ assert "CalculatorButton.vue" in definition["relativePath"], (
+ f"Should point to CalculatorButton.vue, got {definition['relativePath']}"
+ )
assert definition["range"]["start"]["line"] == 0, "Definition should point to start of .vue file"
diff --git a/test/solidlsp/zig/test_zig_basic.py b/test/solidlsp/zig/test_zig_basic.py
index 3753621f..12e23f8c 100644
--- a/test/solidlsp/zig/test_zig_basic.py
+++ b/test/solidlsp/zig/test_zig_basic.py
@@ -193,9 +193,9 @@ class TestZigLanguageServer:
# Verify exact location in main.zig (line 8, 0-indexed: 7)
main_ref_line = main_refs[0]["range"]["start"]["line"]
- assert (
- main_ref_line == 7
- ), f"Calculator reference in main.zig should be at line 8 (0-indexed: 7), found at line {main_ref_line + 1}"
+ assert main_ref_line == 7, (
+ f"Calculator reference in main.zig should be at line 8 (0-indexed: 7), found at line {main_ref_line + 1}"
+ )
@pytest.mark.parametrize("language_server", [Language.ZIG], indirect=True)
def test_cross_file_references_within_file(self, language_server: SolidLanguageServer) -> None:
diff --git a/uv.lock b/uv.lock
index 7a750069..16a5e96e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,6 +1,10 @@
version = 1
revision = 3
-requires-python = "==3.11.*"
+requires-python = ">=3.11, <3.15"
+resolution-markers = [
+ "python_full_version >= '3.13'",
+ "python_full_version < '3.13'",
+]
[[package]]
name = "accessible-pygments"
@@ -16,13 +20,13 @@ wheels = [
[[package]]
name = "agno"
-version = "2.2.1"
+version = "2.5.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "docstring-parser" },
{ name = "gitpython" },
{ name = "h11" },
- { name = "httpx" },
+ { name = "httpx", extra = ["http2"] },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
@@ -33,9 +37,9 @@ dependencies = [
{ name = "typer" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/b2/11aa1934bcffb682686409c592d84cfad342eac2e8af19d86dd292805519/agno-2.2.1.tar.gz", hash = "sha256:9ef9720e03a6b068d7e521f3a571c57475c6a5f5a798c7ccde483f7fee72fd24", size = 1021265, upload-time = "2025-10-23T20:43:49.155Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/39/ac/006e5907991b7c8a99d3ed7b39620a9fa4d3b5d069beba478202fe02d97f/agno-2.5.10.tar.gz", hash = "sha256:a00b2e700ac5661fc0af42996141524f75e2a374b7d7abc411b2bdfe3a9346d7", size = 1797557, upload-time = "2026-03-17T17:15:05.977Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/66/d8/315baab7fc66b3cc89db5e3277729e602a09e7d4ac61b2c3498b193833cf/agno-2.2.1-py3-none-any.whl", hash = "sha256:82311b2afc385810913967109dc5cbb4d3ec1c550aaf22741079fc5e67eb878f", size = 1263574, upload-time = "2025-10-23T20:43:47.355Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/8b/df66cab1b74ea915f847e081abf21a498323580b34312cca6226895aff7f/agno-2.5.10-py3-none-any.whl", hash = "sha256:17ccea453a2007708499265f1df3696ee53ca9a286ca42551d2c5d20d9a41715", size = 2141266, upload-time = "2026-03-17T17:15:03.44Z" },
]
[[package]]
@@ -81,7 +85,7 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
- { name = "typing-extensions" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
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 = [
@@ -177,32 +181,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" },
]
-[[package]]
-name = "black"
-version = "25.1.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "click" },
- { name = "mypy-extensions" },
- { name = "packaging" },
- { 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, 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, 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]
-jupyter = [
- { name = "ipython" },
- { name = "tokenize-rt" },
-]
-
[[package]]
name = "bleach"
version = "6.3.0"
@@ -296,6 +274,52 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
{ url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
{ url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
+ { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
+ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
+ { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
+ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]]
@@ -317,6 +341,32 @@ wheels = [
{ 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/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
+ { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
+ { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
+ { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
+ { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
+ { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
+ { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
+ { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
+ { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
+ { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
+ { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
+ { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
+ { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
{ 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" },
]
@@ -364,49 +414,61 @@ wheels = [
[[package]]
name = "cryptography"
-version = "46.0.3"
+version = "46.0.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
- { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
- { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
- { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
- { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
- { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
- { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
- { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
- { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
- { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
- { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
- { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
- { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
- { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
- { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
- { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
- { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
- { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
- { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
- { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
- { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
- { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
- { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
- { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
- { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
- { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
- { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
- { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
- { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
- { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
- { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" },
- { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" },
- { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" },
- { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" },
- { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" },
- { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" },
+ { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" },
+ { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" },
+ { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" },
+ { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" },
+ { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" },
+ { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" },
+ { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" },
+ { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" },
+ { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" },
+ { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" },
+ { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" },
+ { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" },
+ { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" },
+ { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" },
+ { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" },
+ { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" },
+ { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" },
]
[[package]]
@@ -431,6 +493,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322, upload-time = "2025-09-17T16:33:30.837Z" },
{ url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078, upload-time = "2025-09-17T16:33:33.331Z" },
{ url = "https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4", size = 5179011, upload-time = "2025-09-17T16:33:35.711Z" },
+ { url = "https://files.pythonhosted.org/packages/08/2b/9d8e65beb2751876c82e1aceb32f328c43ec872711fa80257c7674f45650/debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d", size = 2549522, upload-time = "2025-09-17T16:33:38.466Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/78/eb0d77f02971c05fca0eb7465b18058ba84bd957062f5eec82f941ac792a/debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc", size = 4309417, upload-time = "2025-09-17T16:33:41.299Z" },
+ { url = "https://files.pythonhosted.org/packages/37/42/c40f1d8cc1fed1e75ea54298a382395b8b937d923fcf41ab0797a554f555/debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf", size = 5277130, upload-time = "2025-09-17T16:33:43.554Z" },
+ { url = "https://files.pythonhosted.org/packages/72/22/84263b205baad32b81b36eac076de0cdbe09fe2d0637f5b32243dc7c925b/debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464", size = 5319053, upload-time = "2025-09-17T16:33:53.033Z" },
+ { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" },
+ { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" },
+ { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" },
{ url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" },
]
@@ -545,16 +619,16 @@ wheels = [
[[package]]
name = "filelock"
-version = "3.20.0"
+version = "3.25.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" },
]
[[package]]
name = "flask"
-version = "3.1.1"
+version = "3.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blinker" },
@@ -564,9 +638,9 @@ 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, upload-time = "2025-05-13T15:01:17.447Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" },
]
[[package]]
@@ -653,6 +727,28 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" },
+ { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" },
+ { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055, upload-time = "2025-06-05T16:12:40.457Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817, upload-time = "2025-06-05T16:29:49.244Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" },
+ { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" },
+ { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" },
+ { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" },
+ { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" },
+ { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" },
+ { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" },
+ { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" },
]
[[package]]
@@ -664,6 +760,28 @@ wheels = [
{ 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]]
+name = "h2"
+version = "4.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "hpack" },
+ { name = "hyperframe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" },
+]
+
+[[package]]
+name = "hpack"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" },
+]
+
[[package]]
name = "html5lib"
version = "1.1"
@@ -705,6 +823,11 @@ wheels = [
{ 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.optional-dependencies]
+http2 = [
+ { name = "h2" },
+]
+
[[package]]
name = "httpx-sse"
version = "0.4.1"
@@ -714,6 +837,15 @@ wheels = [
{ 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 = "hyperframe"
+version = "6.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
+]
+
[[package]]
name = "idna"
version = "3.10"
@@ -792,7 +924,7 @@ dependencies = [
{ name = "pygments" },
{ name = "stack-data" },
{ name = "traitlets" },
- { name = "typing-extensions" },
+ { name = "typing-extensions", marker = "python_full_version < '3.12'" },
]
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 = [
@@ -862,6 +994,46 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" },
+ { url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" },
+ { url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" },
+ { url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" },
+ { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" },
+ { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" },
+ { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" },
+ { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" },
+ { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" },
+ { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" },
+ { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" },
+ { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" },
+ { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" },
+ { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" },
+ { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" },
]
[[package]]
@@ -1044,6 +1216,36 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
+ { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
+ { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
+ { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
+ { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
+ { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
+ { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
+ { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
]
[[package]]
@@ -1060,7 +1262,7 @@ wheels = [
[[package]]
name = "mcp"
-version = "1.23.0"
+version = "1.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -1078,9 +1280,9 @@ dependencies = [
{ name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/25/1a/9c8a5362e3448d585081d6c7aa95898a64e0ac59d3e26169ae6c3ca5feaf/mcp-1.23.0.tar.gz", hash = "sha256:84e0c29316d0a8cf0affd196fd000487ac512aa3f771b63b2ea864e22961772b", size = 596506, upload-time = "2025-12-02T13:40:02.558Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7b/b2/28739ce409f98159c0121eab56e69ad71546c4f34ac8b42e58c03f57dccc/mcp-1.23.0-py3-none-any.whl", hash = "sha256:5a645cf111ed329f4619f2629a3f15d9aabd7adc2ea09d600d31467b51ecb64f", size = 231427, upload-time = "2025-12-02T13:40:00.738Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" },
]
[[package]]
@@ -1137,6 +1339,42 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" },
{ url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" },
{ url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" },
+ { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" },
+ { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" },
+ { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" },
+ { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" },
+ { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" },
+ { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" },
+ { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" },
+ { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" },
+ { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" },
+ { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" },
+ { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" },
+ { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" },
+ { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" },
+ { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" },
+ { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" },
+ { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" },
+ { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" },
]
[[package]]
@@ -1156,6 +1394,18 @@ wheels = [
{ 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/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" },
+ { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" },
+ { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" },
+ { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" },
+ { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" },
+ { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" },
{ 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" },
]
@@ -1232,7 +1482,7 @@ wheels = [
[[package]]
name = "nbconvert"
-version = "7.16.6"
+version = "7.17.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
@@ -1250,9 +1500,9 @@ dependencies = [
{ name = "pygments" },
{ name = "traitlets" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" },
]
[[package]]
@@ -1388,6 +1638,67 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" },
{ url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" },
{ url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" },
+ { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
+ { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
+ { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
+ { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
+ { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
+ { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
+ { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
+ { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
+ { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
+ { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
+ { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
+ { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
+ { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
+ { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
+ { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
+ { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
+ { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
+ { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
+ { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
+ { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
+ { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
+ { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
{ url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" },
{ url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" },
{ url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" },
@@ -1481,11 +1792,11 @@ wheels = [
[[package]]
name = "pyasn1"
-version = "0.6.1"
+version = "0.6.3"
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, upload-time = "2024-09-10T22:41:42.55Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
]
[[package]]
@@ -1537,7 +1848,7 @@ wheels = [
[[package]]
name = "pydantic"
-version = "2.11.7"
+version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -1545,43 +1856,106 @@ 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, upload-time = "2025-06-14T08:33:17.137Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
]
[[package]]
name = "pydantic-core"
-version = "2.33.2"
+version = "2.41.5"
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, upload-time = "2025-04-23T18:33:52.104Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
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, 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" },
+ { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
+ { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
+ { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
+ { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
+ { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
+ { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
+ { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
+ { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
+ { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
+ { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
+ { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
+ { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
+ { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
+ { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
+ { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
+ { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
+ { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
+ { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
+ { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
+ { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
+ { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
+ { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
+ { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
+ { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
+ { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
+ { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
+ { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
]
[[package]]
@@ -1640,6 +2014,38 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/31/d53b20d1967ba78574807c5940251c0f238e8e3515ee9c4f20207eb09199/pyinstrument-5.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:110a08b6e7fa9542eb37c337e79467913d364a03bc2062f85566ba96bc82f54e", size = 144708, upload-time = "2025-08-12T11:34:38.855Z" },
{ url = "https://files.pythonhosted.org/packages/48/18/78ccb35dd265e83c0b7404a501004387b2e2390a0a44b6e4a004307931d1/pyinstrument-5.1.1-cp311-cp311-win32.whl", hash = "sha256:a223d5e9226ccede5bf2fbd4d13ce0aeb5120501b633ba85290ed94df37d3623", size = 124151, upload-time = "2025-08-12T11:34:40.392Z" },
{ url = "https://files.pythonhosted.org/packages/43/c0/3eb94aa2c2f0b8d49e290d4df662e21e707eb4a23f3b938239634fbfdc17/pyinstrument-5.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:16ce582a7b56287d338a8b59688458341aab5c6abda970ba50b2f7b3fd69f89d", size = 124942, upload-time = "2025-08-12T11:34:41.564Z" },
+ { url = "https://files.pythonhosted.org/packages/76/3a/7824caf1fb419d0108f375a15b28cdd7ace8593f1ea56ef8276fddce9526/pyinstrument-5.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bcd6a03bdf180d73bc8dc7371e09dda089a48057095584e5f2818df1c820525b", size = 130306, upload-time = "2025-08-12T11:34:42.624Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/54/60ddd5eae617e29b58de774d178f4e4f7cdffd07ed1de36f976927ce69d3/pyinstrument-5.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ffa0948c1e268356dcf930c128624f34037ce92ee865fa4c056dee067aee4c5", size = 122817, upload-time = "2025-08-12T11:34:44.182Z" },
+ { url = "https://files.pythonhosted.org/packages/35/12/35b694bfa58050607eedc80a68f64e6195c738249101a0dcbed0657147e7/pyinstrument-5.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c95adf98a920f2039eb0065966f980452a7af794bab387e9bfe8af3c681affa0", size = 148053, upload-time = "2025-08-12T11:34:45.589Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/4a/338b891f9119cf747153301d5d095942f378032309cd385e53857d03c2d2/pyinstrument-5.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcb46ca8596b375c27850d4d06a1ce94ed78074774d35cbed3ccd28b663c5ba6", size = 146817, upload-time = "2025-08-12T11:34:46.668Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/cc/186cdb048fee445bbf9bd18819287a61b57b66ec68cfc47bc3c1e38b1ae6/pyinstrument-5.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3fc16597d26b24a46bf3455686300c0b8a3eb565ebc82396f402c031dccc0145", size = 146914, upload-time = "2025-08-12T11:34:47.878Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/d1/533309830dd356d43e54d7feebaffab357f08568972285c609e98f7e6119/pyinstrument-5.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5aa135b4bd9667ddcb25fa582f4db77c5117ef207cb10ae901a8e4c5d5cde0e0", size = 146533, upload-time = "2025-08-12T11:34:49.015Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/df/2a656d6b1bd68ecfbb73c557906274a40ec7219dd92980fc1324997cf93e/pyinstrument-5.1.1-cp312-cp312-win32.whl", hash = "sha256:d15e37f8074b3043fca7aa985cb2079d2c221ccb0d27f059451ede800c801645", size = 124286, upload-time = "2025-08-12T11:34:50.285Z" },
+ { url = "https://files.pythonhosted.org/packages/51/af/144d331cc9734e9141ac1a75f3ce904074ebc93dfe43cab44049ba8c8c28/pyinstrument-5.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c27d5cef0e809f213e5a94143c397d948650f5142c91dcce3611f584779183e", size = 125032, upload-time = "2025-08-12T11:34:51.369Z" },
+ { url = "https://files.pythonhosted.org/packages/36/d4/b94f47aa7d301f6cdf5924bb75caacd0d0a1852bd4e876e3a64fc5798dad/pyinstrument-5.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:45af421c60c943a7f1619afabeba4951d4cc16b4206490d7d5b7ef5a4e2dfd42", size = 130315, upload-time = "2025-08-12T11:34:52.91Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/42/1bc2f28e139f69a0918d5d5dc1d59e65c640d4da9dd153fa48c2a8a87dd9/pyinstrument-5.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2603db3d745a65de66c96929ab9b0fcce050511eb24e32856ea2458785b8917f", size = 122805, upload-time = "2025-08-12T11:34:54.201Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/85/2f0c9115cd8a01e0a18d0650d9f3f20ff71e8ca17bd4af60dd3a0cb76f8a/pyinstrument-5.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fe32492100efaa1b0a488c237fe420fdaf141646733a31a97f96c4e1fa6bbf8", size = 148210, upload-time = "2025-08-12T11:34:55.662Z" },
+ { url = "https://files.pythonhosted.org/packages/86/62/3c73a63e6913378cc7e9ffb5af1e50836511eee83b7c7bf252fad7ec24e4/pyinstrument-5.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:999b5373f8b1e846357923063ae5c9275ad8a85ed4e0a42960a349288d1f5007", size = 146995, upload-time = "2025-08-12T11:34:57.133Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/8b/d21f4b6d8849881e9572967818e3e6d2dcb212e7dfa89e4e356d359db32b/pyinstrument-5.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58a2f69052178ec624e4df0cf546eda48b3a381572ac1cb3272b4c163888af9d", size = 147029, upload-time = "2025-08-12T11:34:58.255Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/4d/1e43cecf2bcf4a3dd1100f4fc7a3da6438a65d0b95ca7b8ab5d094ea7c0b/pyinstrument-5.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d9bbc00d2e258edbefeb39b61ad4636099b08acd1effdd40d76883a13e7bf5a", size = 146668, upload-time = "2025-08-12T11:34:59.401Z" },
+ { url = "https://files.pythonhosted.org/packages/34/48/00322b48e7adb665d04303b487454eb0c13a76ec0af8da20f452098fcc12/pyinstrument-5.1.1-cp313-cp313-win32.whl", hash = "sha256:cf2d8933e2aeaa02d4cb6279d83ef11ee882fb243fff96e3378153a730aadd6e", size = 124288, upload-time = "2025-08-12T11:35:00.514Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/14/d56515a110f74799aefc7489c1578ce4d99a4d731309559a427f954e7abc/pyinstrument-5.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:2402683a92617066b13a6d48f904396dcd15938016875b392534df027660eed4", size = 125041, upload-time = "2025-08-12T11:35:01.913Z" },
+ { url = "https://files.pythonhosted.org/packages/18/2b/e4bdcabb5ae67de2ec3fa1f6e4eb4ae707b0bf460f895d4594792cdc919b/pyinstrument-5.1.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:688acba1c00cad73e43254e610f8e384a53ced3b0dbb5268fb44636e2b99663e", size = 130358, upload-time = "2025-08-12T11:35:03.569Z" },
+ { url = "https://files.pythonhosted.org/packages/20/36/616f8db63997c096d3fb65e657cdf5bd2a63b53ed24a14750770dc500979/pyinstrument-5.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:116f5ad8cec4d6f5626305d7c1a104f5845a084bfb4b192d231eb8c41ea81f9a", size = 122827, upload-time = "2025-08-12T11:35:04.661Z" },
+ { url = "https://files.pythonhosted.org/packages/af/7a/4f5d2bbc7c2466d46eb5ff47c6e667464eead47140e01a64be45215a59d4/pyinstrument-5.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d139d12a637001d3884344330054ce8335b2c8165dc3dd239726e1b358576bd", size = 147947, upload-time = "2025-08-12T11:35:05.786Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8c/c9b0081c0e52789a910390ce44e54c1318999d74386f15d92d0deb522aff/pyinstrument-5.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc5b87b1e27bec94457fed8d03c755a3c09edb4f35d975dbdffd77d863173254", size = 146702, upload-time = "2025-08-12T11:35:07.202Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/1b/745ed7997da22ae68ff21b8f28e5e3a97b220335dce4ee7cf46d5eb17b32/pyinstrument-5.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15f4a2ed9562efab34b555e1208955cf9681b2272489d7a59cd0e289344ada2e", size = 146836, upload-time = "2025-08-12T11:35:08.297Z" },
+ { url = "https://files.pythonhosted.org/packages/70/f0/05cefdcf79d1901f9d179e7f55f3acaadbc5fee7af955cebb3f555280638/pyinstrument-5.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1cb0c79bfa2b2b5734213429c9d7f455e5af664cfde785c69a5780f6c532c1fd", size = 146463, upload-time = "2025-08-12T11:35:09.483Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/cb/6a6f33316be3c7b8247f8ca0e418a2b6fb68d64c227169b7dbee50009366/pyinstrument-5.1.1-cp314-cp314-win32.whl", hash = "sha256:3b9f1216ae4848a8983dc405e1a42e46e75bd8ae96aaba328d4358b8fc80a7a0", size = 124950, upload-time = "2025-08-12T11:35:11.607Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/ea/99caeb29f446f57d077a83c7c5f2b7c27c1719984d425f679bf2ec1eb6b0/pyinstrument-5.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:26971d4a17e0d5d4f6737e71c9de7a7ce5c83ab7daf078c6bf330be41d65273b", size = 125720, upload-time = "2025-08-12T11:35:12.683Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/d0/953b75d634565ef34f8ed559f2e4af7cd1f2d5f5b578092e8f1d8199e4b1/pyinstrument-5.1.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:62362843884d654401ec4c25fed35f4b4ded077d96b3396f1e791c31e4203d3e", size = 131258, upload-time = "2025-08-12T11:35:13.805Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/a4/4ec87cfd0974d79b2fcd72b3e20336fc65b96a5b08f2eb2867bf71b27b82/pyinstrument-5.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f2d640230b71c6d9ac8f27a9c5cd07fc8a6acad9196d1e48d9c33658b176fb80", size = 123276, upload-time = "2025-08-12T11:35:14.933Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/f8/6a210989c8ede85f91b7e4ba5d9730492f1d081762570c06c750d787536c/pyinstrument-5.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f54f7292c63461c75ddf193f5e733803e463ccbc54f2fb7c9591337ddea7d10", size = 155767, upload-time = "2025-08-12T11:35:16.124Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/a8/5ac81ffbfe36d2e5c3332a9452746a21540987da0d9491db751a905bba13/pyinstrument-5.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c156eb442f9f22960ae16bd195051863d5e8a68b877926e88bbaf8bbdc1456d1", size = 153423, upload-time = "2025-08-12T11:35:17.312Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/55/5620c2a61403cde044e81e33056c14fbf5793eea33f67f2223d61abec9ae/pyinstrument-5.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:caadaf67ad5926c46af784316024793c909b9e9ee550475855fd32171c4bd033", size = 153542, upload-time = "2025-08-12T11:35:18.729Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/83/a8f22466652250a847dfdf58f9a2717b470fdbbcb075c7f730bf608041a6/pyinstrument-5.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88ef2e8f483a5e1501d79a7ebdab592a597467810ed24d8db09ab6f568e938d3", size = 152337, upload-time = "2025-08-12T11:35:19.849Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/a6/cd4590da14deaeda6315519c26064874bbb9648a1358b80e8a8ca5d4add0/pyinstrument-5.1.1-cp314-cp314t-win32.whl", hash = "sha256:265bc4389f82e6521777bfab426a62a15c4940955e86f75db79a44e7349f9757", size = 125621, upload-time = "2025-08-12T11:35:21.201Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/30/177102e798539368aef25688a6a171d66ec92e6f16b6b651a89045a2bd13/pyinstrument-5.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fa254f269a72a007b5d02c18cd4b67081e0efabbd33e18acdbd5e3be905afa06", size = 126528, upload-time = "2025-08-12T11:35:22.578Z" },
]
[[package]]
@@ -1663,6 +2069,11 @@ source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" },
+ { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" },
+ { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" },
+ { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" },
]
[[package]]
@@ -1675,6 +2086,11 @@ dependencies = [
sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" },
+ { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" },
+ { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" },
+ { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" },
+ { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" },
]
[[package]]
@@ -1688,6 +2104,11 @@ dependencies = [
sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" },
+ { url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" },
]
[[package]]
@@ -1701,6 +2122,11 @@ dependencies = [
sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" },
+ { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" },
+ { url = "https://files.pythonhosted.org/packages/32/48/b294ed75247c5cfa00d51925a10237337d24f54961d49a179b20a4307642/pyobjc_framework_security-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afc36661cc6eb98cd794bed1d6668791e96557d6f72d9ac70aa49022d26af1d4", size = 41284, upload-time = "2025-11-14T10:03:01.722Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/57/0d3ef78779cf5c3bba878b2f824137e50978ad4a21dabe65d8b5ae0fc0d1/pyobjc_framework_security-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9510c98ab56921d1d416437372605cc1c1f6c1ad8d3061ee56b17bf423dd5427", size = 42162, upload-time = "2025-11-14T10:03:05.337Z" },
+ { url = "https://files.pythonhosted.org/packages/66/4d/63c15f9449c191e7448a05ff8af4a82c39a51bb627bc96dc9697586c0f79/pyobjc_framework_security-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6319a34508fd87ab6ca3cda6f54e707196197a65b792b292705af967e225438a", size = 41348, upload-time = "2025-11-14T10:03:08.926Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d8/5aaa2a8124ed04a9d6ca7053dc0fa64e42be51497ed8263a24b744a95598/pyobjc_framework_security-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:03d166371cefdef24908825148eb848f99ee2c0b865870a09dcbb94334dd3e0a", size = 42908, upload-time = "2025-11-14T10:03:13.01Z" },
]
[[package]]
@@ -1727,6 +2153,11 @@ dependencies = [
sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" },
+ { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/3d/80d36280164c69220ce99372f7736a028617c207e42cb587716009eecb88/pyobjc_framework_webkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da0c428c9d9891c93e0de51c9f272bfeb96d34356cdf3136cb4ad56ce32ec2d", size = 50096, upload-time = "2025-11-14T10:07:10.027Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/7a/03c29c46866e266b0c705811c55c22625c349b0a80f5cf4776454b13dc4c/pyobjc_framework_webkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a29e334d5a7dd4a4f0b5647481b6ccf8a107b92e67b2b3c6b368c899f571965", size = 50572, upload-time = "2025-11-14T10:07:14.232Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/ac/924878f239c167ffe3bfc643aee4d6dd5b357e25f6b28db227e40e9e6df3/pyobjc_framework_webkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:99d0d28542a266a95ee2585f51765c0331794bca461aaf4d1f5091489d475179", size = 50210, upload-time = "2025-11-14T10:07:18.926Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/86/637cda4983dc0936b73a385f3906256953ac434537b812814cb0b6d231a2/pyobjc_framework_webkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1aaa3bf12c7b68e1a36c0b294d2728e06f2cc220775e6dc4541d5046290e4dc8", size = 50680, upload-time = "2025-11-14T10:07:23.331Z" },
]
[[package]]
@@ -1811,20 +2242,20 @@ wheels = [
[[package]]
name = "python-dotenv"
-version = "1.1.1"
+version = "1.2.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, upload-time = "2025-06-24T04:21:07.341Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
]
[[package]]
name = "python-multipart"
-version = "0.0.20"
+version = "0.0.22"
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, upload-time = "2024-12-16T19:45:46.972Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
]
[[package]]
@@ -1877,6 +2308,15 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
+ { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
+ { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
]
[[package]]
@@ -1894,6 +2334,24 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
+ { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
+ { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
+ { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
+ { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
+ { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
]
[[package]]
@@ -1925,6 +2383,28 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" },
{ url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" },
{ url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" },
+ { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" },
+ { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" },
+ { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" },
+ { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" },
+ { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" },
+ { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" },
{ url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" },
{ url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" },
{ url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" },
@@ -1951,7 +2431,7 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
- { name = "typing-extensions" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
wheels = [
@@ -1979,11 +2459,41 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" },
+ { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" },
+ { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" },
+ { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" },
+ { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" },
+ { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" },
+ { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" },
+ { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" },
+ { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" },
+ { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" },
+ { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" },
+ { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" },
+ { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" },
+ { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" },
+ { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" },
]
[[package]]
name = "requests"
-version = "2.32.4"
+version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -1991,9 +2501,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, upload-time = "2025-06-09T16:43:07.34Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
@@ -2029,6 +2539,74 @@ wheels = [
{ 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/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" },
+ { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" },
+ { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" },
+ { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" },
+ { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" },
+ { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214, upload-time = "2025-07-01T15:54:37.469Z" },
+ { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113, upload-time = "2025-07-01T15:54:38.954Z" },
+ { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189, upload-time = "2025-07-01T15:54:40.57Z" },
+ { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998, upload-time = "2025-07-01T15:54:43.025Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903, upload-time = "2025-07-01T15:54:44.752Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785, upload-time = "2025-07-01T15:54:46.043Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329, upload-time = "2025-07-01T15:54:47.64Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875, upload-time = "2025-07-01T15:54:48.9Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636, upload-time = "2025-07-01T15:54:50.619Z" },
+ { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663, upload-time = "2025-07-01T15:54:52.023Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428, upload-time = "2025-07-01T15:54:53.692Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571, upload-time = "2025-07-01T15:54:54.822Z" },
+ { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415, upload-time = "2025-07-01T15:54:59.751Z" },
+ { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783, upload-time = "2025-07-01T15:55:00.898Z" },
+ { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844, upload-time = "2025-07-01T15:55:02.201Z" },
+ { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105, upload-time = "2025-07-01T15:55:03.698Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440, upload-time = "2025-07-01T15:55:05.398Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759, upload-time = "2025-07-01T15:55:08.316Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032, upload-time = "2025-07-01T15:55:09.52Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416, upload-time = "2025-07-01T15:55:11.216Z" },
+ { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" },
+ { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" },
+ { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" },
+ { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" },
+ { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" },
+ { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" },
+ { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" },
+ { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" },
+ { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" },
+ { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" },
+ { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" },
+ { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" },
+ { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" },
+ { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" },
+ { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" },
{ 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" },
@@ -2059,7 +2637,7 @@ name = "ruamel-yaml"
version = "0.18.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" },
+ { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and 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, upload-time = "2025-06-09T08:51:09.828Z" }
wheels = [
@@ -2081,6 +2659,24 @@ wheels = [
{ 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" },
+ { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" },
+ { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" },
+ { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" },
+ { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" },
+ { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011, upload-time = "2024-10-20T10:13:04.377Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" },
+ { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" },
+ { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" },
+ { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583, upload-time = "2024-10-20T10:13:09.658Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" },
]
[[package]]
@@ -2127,8 +2723,10 @@ source = { editable = "." }
dependencies = [
{ name = "anthropic" },
{ name = "beautifulsoup4" },
+ { name = "cryptography" },
{ name = "docstring-parser" },
{ name = "dotenv" },
+ { name = "filelock" },
{ name = "flask" },
{ name = "fortls" },
{ name = "jinja2" },
@@ -2141,14 +2739,18 @@ dependencies = [
{ name = "pyright" },
{ name = "pystray" },
{ name = "python-dotenv" },
+ { name = "python-multipart" },
{ name = "pywebview" },
{ name = "pyyaml" },
{ name = "requests" },
{ name = "ruamel-yaml" },
{ name = "sensai-utils" },
+ { name = "starlette" },
{ name = "tiktoken" },
{ name = "tqdm" },
{ name = "types-pyyaml" },
+ { name = "urllib3" },
+ { name = "werkzeug" },
]
[package.optional-dependencies]
@@ -2157,13 +2759,15 @@ agno = [
{ name = "sqlalchemy" },
]
dev = [
- { name = "black", extra = ["jupyter"] },
{ name = "jinja2" },
{ name = "jupyter-book" },
{ name = "mypy" },
+ { name = "nbconvert" },
{ name = "nbsphinx" },
{ name = "poethepoet" },
+ { name = "pyasn1" },
{ name = "pyinstrument" },
+ { name = "pyjwt" },
{ name = "pytest" },
{ name = "pytest-timeout" },
{ name = "pytest-xdist" },
@@ -2173,8 +2777,10 @@ dev = [
{ name = "sphinx-toolbox" },
{ name = "syrupy" },
{ name = "toml-sort" },
+ { name = "tornado" },
{ name = "types-pyyaml" },
{ name = "types-requests" },
+ { name = "wheel" },
]
google = [
{ name = "google-genai" },
@@ -2182,51 +2788,61 @@ google = [
[package.metadata]
requires-dist = [
- { name = "agno", marker = "extra == 'agno'", specifier = ">=2.2.1" },
- { name = "anthropic", specifier = ">=0.54.0" },
- { name = "beautifulsoup4", specifier = ">=4.14.2" },
- { name = "black", extras = ["jupyter"], marker = "extra == 'dev'", specifier = ">=23.7.0,<26" },
- { 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'" },
- { name = "joblib", specifier = ">=1.5.1" },
- { name = "jupyter-book", marker = "extra == 'dev'", specifier = ">=1,<2" },
- { name = "mcp", specifier = "==1.23.0" },
- { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.16.1" },
- { name = "nbsphinx", marker = "extra == 'dev'" },
- { name = "overrides", specifier = ">=7.7.0,<8" },
- { name = "pathspec", specifier = ">=0.12.1" },
- { name = "poethepoet", marker = "extra == 'dev'", specifier = ">=0.20.0" },
- { name = "psutil", specifier = ">=7.0.0" },
- { name = "pydantic", specifier = ">=2.10.6" },
- { name = "pyinstrument", marker = "extra == 'dev'" },
- { name = "pyright", specifier = ">=1.1.396,<2" },
+ { name = "agno", marker = "extra == 'agno'", specifier = "==2.5.10" },
+ { name = "anthropic", specifier = "==0.59.0" },
+ { name = "beautifulsoup4", specifier = "==4.14.2" },
+ { name = "cryptography", specifier = "==46.0.5" },
+ { name = "docstring-parser", specifier = "==0.17.0" },
+ { name = "dotenv", specifier = "==0.9.9" },
+ { name = "filelock", specifier = "==3.25.2" },
+ { name = "flask", specifier = "==3.1.3" },
+ { name = "fortls", specifier = "==3.2.2" },
+ { name = "google-genai", marker = "extra == 'google'", specifier = "==1.27.0" },
+ { name = "jinja2", specifier = "==3.1.6" },
+ { name = "jinja2", marker = "extra == 'dev'", specifier = "==3.1.6" },
+ { name = "joblib", specifier = "==1.5.1" },
+ { name = "jupyter-book", marker = "extra == 'dev'", specifier = "==1.0.4.post1" },
+ { name = "mcp", specifier = "==1.26.0" },
+ { name = "mypy", marker = "extra == 'dev'", specifier = "==1.17.0" },
+ { name = "nbconvert", marker = "extra == 'dev'", specifier = "==7.17.0" },
+ { name = "nbsphinx", marker = "extra == 'dev'", specifier = "==0.9.7" },
+ { name = "overrides", specifier = "==7.7.0" },
+ { name = "pathspec", specifier = "==0.12.1" },
+ { name = "poethepoet", marker = "extra == 'dev'", specifier = "==0.36.0" },
+ { name = "psutil", specifier = "==7.0.0" },
+ { name = "pyasn1", marker = "extra == 'dev'", specifier = "==0.6.3" },
+ { name = "pydantic", specifier = "==2.12.5" },
+ { name = "pyinstrument", marker = "extra == 'dev'", specifier = "==5.1.1" },
+ { name = "pyjwt", marker = "extra == 'dev'", specifier = "==2.10.1" },
+ { name = "pyright", specifier = "==1.1.403" },
{ name = "pystray", specifier = ">=0.19.5" },
- { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.2" },
- { name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.4.0" },
- { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.5.0" },
- { name = "python-dotenv", specifier = ">=1.0.0,<2" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = "==8.4.1" },
+ { name = "pytest-timeout", marker = "extra == 'dev'", specifier = "==2.4.0" },
+ { name = "pytest-xdist", marker = "extra == 'dev'", specifier = "==3.8.0" },
+ { name = "python-dotenv", specifier = "==1.2.1" },
+ { name = "python-multipart", specifier = "==0.0.22" },
{ name = "pywebview", git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe" },
- { name = "pyyaml", specifier = ">=6.0.2" },
- { name = "requests", specifier = ">=2.32.3,<3" },
+ { name = "pyyaml", specifier = "==6.0.2" },
+ { name = "requests", specifier = "==2.32.5" },
{ name = "ruamel-yaml", specifier = "==0.18.14" },
- { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.0.285" },
- { name = "sensai-utils", specifier = ">=1.5.0" },
- { name = "sphinx", marker = "extra == 'dev'", specifier = ">=7,<8" },
- { name = "sphinx-rtd-theme", marker = "extra == 'dev'", specifier = ">=0.5.1" },
+ { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.5" },
+ { name = "sensai-utils", specifier = "==1.5.0" },
+ { name = "sphinx", marker = "extra == 'dev'", specifier = "==7.4.7" },
+ { name = "sphinx-rtd-theme", marker = "extra == 'dev'", specifier = "==2.0.0" },
{ name = "sphinx-toolbox", marker = "extra == 'dev'", specifier = "==3.7.0" },
- { name = "sqlalchemy", marker = "extra == 'agno'", specifier = ">=2.0.40" },
- { name = "syrupy", marker = "extra == 'dev'", specifier = ">=4.9.1" },
- { name = "tiktoken", specifier = ">=0.9.0" },
- { name = "toml-sort", marker = "extra == 'dev'", specifier = ">=0.24.2" },
- { name = "tqdm", specifier = ">=4.67.1" },
- { name = "types-pyyaml", specifier = ">=6.0.12.20241230" },
- { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.12.20241230" },
- { name = "types-requests", marker = "extra == 'dev'", specifier = ">=2.32.4.20241230" },
+ { name = "sqlalchemy", marker = "extra == 'agno'", specifier = "==2.0.41" },
+ { name = "starlette", specifier = "==1.0.0" },
+ { name = "syrupy", marker = "extra == 'dev'", specifier = "==4.9.1" },
+ { name = "tiktoken", specifier = "==0.12.0" },
+ { name = "toml-sort", marker = "extra == 'dev'", specifier = "==0.24.2" },
+ { name = "tornado", marker = "extra == 'dev'", specifier = "==6.5.5" },
+ { name = "tqdm", specifier = "==4.67.1" },
+ { name = "types-pyyaml", specifier = "==6.0.12.20250516" },
+ { name = "types-pyyaml", marker = "extra == 'dev'", specifier = "==6.0.12.20250516" },
+ { name = "types-requests", marker = "extra == 'dev'", specifier = "==2.32.4.20250809" },
+ { name = "urllib3", specifier = "==2.6.3" },
+ { name = "werkzeug", specifier = "==3.1.7" },
+ { name = "wheel", marker = "extra == 'dev'", specifier = "==0.46.3" },
]
provides-extras = ["dev", "agno", "google"]
@@ -2402,6 +3018,7 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "markupsafe" },
+ { name = "standard-imghdr", marker = "python_full_version >= '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/98/43313781f29e8c6c46fec907430310172d6f207e95e4fbea9289990fbbfe/sphinx_jinja2_compat-0.4.1.tar.gz", hash = "sha256:0188f0802d42c3da72997533b55a00815659a78d3f81d4b4747b1fb15a5728e6", size = 5222, upload-time = "2025-08-06T20:06:25.824Z" }
wheels = [
@@ -2435,7 +3052,7 @@ wheels = [
[[package]]
name = "sphinx-prompt"
-version = "1.10.1"
+version = "1.10.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -2447,9 +3064,9 @@ dependencies = [
{ name = "sphinx" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6c/1b/ceb8d295275982121eefc8a4f0b71edf7f26c16f5bc53f4204527f173783/sphinx_prompt-1.10.1.tar.gz", hash = "sha256:2b8a18433ee3d9f2a7c4e51a768fb14c5177cce4bf0e2298ecc068a0ca9918bf", size = 5242, upload-time = "2025-08-13T09:21:35.971Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/a3/91293c0e0f0b76d0697ba7a41541929ca3f5457671d008bd84a9bde17e21/sphinx_prompt-1.10.2.tar.gz", hash = "sha256:47b592ba75caebd044b0eddf7a5a1b6e0aef6df587b034377cd101a999b686ba", size = 5566, upload-time = "2025-11-28T09:23:18.057Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/33/23/dca289e67226222ce988f7a4a31cae0c0f4752338ead0c1e509203658080/sphinx_prompt-1.10.1-py3-none-any.whl", hash = "sha256:6cd34abbeedc14446bba76036b1821da142052f7043c5a8a3d6b84565ee54900", size = 7428, upload-time = "2025-08-13T09:21:35.192Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f4/44ce4d0179fb4e9cfe181a8aa281bba23e40158a609fb3680774529acaaa/sphinx_prompt-1.10.2-py3-none-any.whl", hash = "sha256:6594337962c4b1498602e6984634bed4a0dc7955852e3cfc255eb0af766ed859", size = 7474, upload-time = "2025-11-28T09:23:17.154Z" },
]
[[package]]
@@ -2621,7 +3238,7 @@ name = "sqlalchemy"
version = "2.0.41"
source = { registry = "https://pypi.org/simple" }
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 = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and 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, upload-time = "2025-05-14T17:10:32.339Z" }
@@ -2634,6 +3251,22 @@ wheels = [
{ 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/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645, upload-time = "2025-05-14T17:55:24.854Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399, upload-time = "2025-05-14T17:55:28.097Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269, upload-time = "2025-05-14T17:50:38.227Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/35/f74add3978c20de6323fb11cb5162702670cc7a9420033befb43d8d5b7a4/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", size = 3303364, upload-time = "2025-05-14T17:51:49.829Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/d4/c990f37f52c3f7748ebe98883e2a0f7d038108c2c5a82468d1ff3eec50b7/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", size = 3229072, upload-time = "2025-05-14T17:50:39.774Z" },
+ { url = "https://files.pythonhosted.org/packages/15/69/cab11fecc7eb64bc561011be2bd03d065b762d87add52a4ca0aca2e12904/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", size = 3268074, upload-time = "2025-05-14T17:51:51.736Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ca/0c19ec16858585d37767b167fc9602593f98998a68a798450558239fb04a/sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", size = 2084514, upload-time = "2025-05-14T17:55:49.915Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/23/4c2833d78ff3010a4e17f984c734f52b531a8c9060a50429c9d4b0211be6/sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", size = 2111557, upload-time = "2025-05-14T17:55:51.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" },
+ { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" },
{ 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" },
]
@@ -2663,17 +3296,26 @@ wheels = [
{ 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]]
+name = "standard-imghdr"
+version = "3.10.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/09/d2/2eb5521072c9598886035c65c023f39f7384bcb73eed70794f469e34efac/standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52", size = 5474, upload-time = "2024-04-21T18:55:10.859Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/d0/9852f70eb01f814843530c053542b72d30e9fbf74da7abb0107e71938389/standard_imghdr-3.10.14-py3-none-any.whl", hash = "sha256:cdf6883163349624dee9a81d2853a20260337c4cd41c04e99c082e01833a08e2", size = 5598, upload-time = "2024-04-21T18:54:48.587Z" },
+]
+
[[package]]
name = "starlette"
-version = "0.47.2"
+version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
- { name = "typing-extensions" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-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" }
+sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
]
[[package]]
@@ -2708,20 +3350,56 @@ wheels = [
[[package]]
name = "tiktoken"
-version = "0.9.0"
+version = "0.12.0"
source = { registry = "https://pypi.org/simple" }
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, upload-time = "2025-02-14T06:03:01.003Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" }
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, 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" },
+ { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565, upload-time = "2025-10-06T20:21:44.566Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284, upload-time = "2025-10-06T20:21:45.622Z" },
+ { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201, upload-time = "2025-10-06T20:21:47.074Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444, upload-time = "2025-10-06T20:21:48.139Z" },
+ { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080, upload-time = "2025-10-06T20:21:49.246Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240, upload-time = "2025-10-06T20:21:50.274Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422, upload-time = "2025-10-06T20:21:51.734Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" },
+ { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" },
+ { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" },
+ { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" },
+ { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" },
+ { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" },
+ { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" },
+ { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" },
+ { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" },
+ { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" },
+ { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" },
+ { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" },
+ { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" },
+ { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" },
+ { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" },
]
[[package]]
@@ -2736,15 +3414,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
]
-[[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, 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, upload-time = "2025-05-23T23:47:58.812Z" },
-]
-
[[package]]
name = "toml-sort"
version = "0.24.2"
@@ -2768,21 +3437,19 @@ wheels = [
[[package]]
name = "tornado"
-version = "6.5.2"
+version = "6.5.5"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" },
- { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" },
- { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" },
- { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" },
- { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" },
- { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" },
- { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" },
- { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" },
- { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" },
- { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" },
- { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" },
+ { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" },
+ { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" },
+ { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" },
+ { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" },
]
[[package]]
@@ -2853,14 +3520,14 @@ wheels = [
[[package]]
name = "typing-inspection"
-version = "0.4.1"
+version = "0.4.2"
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, upload-time = "2025-05-21T18:55:23.885Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
@@ -2874,11 +3541,11 @@ wheels = [
[[package]]
name = "urllib3"
-version = "2.5.0"
+version = "2.6.3"
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, upload-time = "2025-06-18T14:07:41.644Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
]
[[package]]
@@ -2929,28 +3596,53 @@ wheels = [
{ 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/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
+ { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
+ { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
+ { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
+ { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
+ { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
+ { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
+ { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
+ { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
{ 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]]
name = "werkzeug"
-version = "3.1.3"
+version = "3.1.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, upload-time = "2024-11-08T15:52:18.093Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/43/76ded108b296a49f52de6bac5192ca1c4be84e886f9b5c9ba8427d9694fd/werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351", size = 875700, upload-time = "2026-03-24T01:08:07.687Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" },
]
[[package]]
name = "wheel"
-version = "0.45.1"
+version = "0.46.3"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605, upload-time = "2026-01-22T12:39:49.136Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" },
+ { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557, upload-time = "2026-01-22T12:39:48.099Z" },
]
[[package]]