Replace mypy by ty (#1619)

This commit is contained in:
Michael Panchenko authored and GitHub committed 2026-06-29 12:26:07 +02:00
1 parent 0d8d5add9d
commit 7333a0c868
53 files changed
+221 -210

No files matched your search

+3 -5
View File
@@ -13,11 +13,9 @@
- `ruff format` runs on `src scripts test`; same set for `ruff check`.
- mccabe complexity cap: 20.
## Typing (mypy strict)
- `disallow_untyped_defs = true`, `disallow_incomplete_defs = true`, `strict_equality`, `strict_optional`, `warn_unreachable`, `no_implicit_optional`.
- Excluded paths: `^build/`, `^docs/`, `^test/resources/`.
- Test files relax `disallow_untyped_defs` (still type-checked otherwise).
## Typing (ty)
- Type checker is **ty** (Astral), configured under `[tool.ty]` in `pyproject.toml`.
## Tests
- Language-server tests are pytest-marker-gated (one marker per language; see `pyproject.toml` `[tool.pytest.ini_options].markers`). Default `poe test` runs unmarked tests + whatever `PYTEST_MARKERS` selects.
- Snapshot tests use **syrupy** with custom `--snapshot-patch-pycharm-diff` plugin (auto-added via `addopts`).
@@ -104,7 +104,7 @@ The project uses modern Python tooling:
- **uv** for fast dependency resolution and virtual environments
- **pytest** for testing with language-specific markers (`@pytest.mark.csharp`)
- **ruff** for linting and formatting
- **mypy** for type checking
- **ty** (Astral) for type checking
## Build and Development
+1 -1
View File
@@ -6,7 +6,7 @@ Run via `uv run poe <task>` (or `poe <task>` inside the activated venv). Poe exe
- `poe test` — pytest on `test/` (per-language tests are marker-gated; pass `-m <marker>` to enable).
- `poe lint` — ruff format-check + ruff check (no fixes).
- `poe format` — ruff `--fix` then `ruff format` (mutates files).
- `poe type-check` — mypy on `src/serena`, `src/solidlsp`, and `test/` (test pass disables `no-untyped-def`).
- `poe type-check` — ty on `src/serena`, `src/solidlsp`, and `test/` (test pass relaxes pytest/mock-noisy rules via `[[tool.ty.overrides]]`).
- Single test file: `uv run pytest test/path/to/test_x.py -vv`. Language-gated: add `-m python` etc.
## Docs
+1 -1
View File
@@ -3,7 +3,7 @@
After any code change in `src/` or `test/`, run:
1. `uv run poe format` — applies ruff fixes + formatting (mutates files).
2. `uv run poe type-check` — mypy strict on `src/serena`, `src/solidlsp`, `test/`.
2. `uv run poe type-check` — ty on `src/serena`, `src/solidlsp`, `test/`.
3. `uv run poe test` — pytest on affected files or affected languages, using `-m` markers.
If prompt templates changed: `uv run python scripts/gen_prompt_factory.py` (regenerates `src/serena/generated/generated_prompt_factory.py`; use `uv run poe format` and commit the result).
+1 -1
View File
@@ -5,6 +5,6 @@
- Build backend: `hatchling`. Packages: `src/serena`, `src/interprompt`, `src/solidlsp`.
- Task runner: **poethepoet** (`poe <task>`). Poe executor is `simple` (does NOT shell out via uv) — avoids env recreation while MCP server is running.
- Key runtime deps: `mcp`, `flask` (dashboard), `pydantic`, `pygls` + `lsprotocol` (LSP), `anthropic`, `jinja2`, `ruamel.yaml`, `pywebview`/`pystray` (GUI), `pythonnet` (Windows only).
- Dev deps: `ruff` (lint+format), `mypy` (strict), `pytest` (+ `pytest-xdist`, `pytest-timeout`, `syrupy` snapshots), `sphinx`/`jupyter-book` for docs.
- Dev deps: `ruff` (lint+format), `ty` (Astral type checker; replaced mypy), `pytest` (+ `pytest-xdist`, `pytest-timeout`, `syrupy` snapshots), `sphinx`/`jupyter-book` for docs.
- Optional extras: `agno` (Agno agent integration), `google` (gemini).
- LSP client core lives under `src/solidlsp/`; one subdir per supported language server under `language_servers/`.
+44 -26
View File
@@ -73,7 +73,7 @@ text = "MIT"
dev = [
"jinja2==3.1.6",
# In version 1.0.4 we get a NoneType error related to some config conversion (yml_analytics is None and should be a list)
"mypy==1.17.0",
"ty==0.0.24",
"poethepoet==0.36.0",
"pytest==8.4.1",
"pytest-xdist==3.8.0",
@@ -109,29 +109,47 @@ packages = ["src/serena", "src/interprompt", "src/solidlsp"]
[tool.doc8]
max-line-length = 1000
[tool.mypy]
allow_redefinition = true
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
ignore_missing_imports = true
no_implicit_optional = true
pretty = true
show_error_codes = true
show_error_context = true
show_traceback = true
strict_equality = true
strict_optional = true
warn_no_return = true
warn_redundant_casts = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = false
exclude = "^build/|^docs/|^test/resources/"
[tool.ty.environment]
python-version = "3.11"
# Analyze for all platforms rather than defaulting to the OS ty happens to run on. This keeps the
# check deterministic across the CI matrix (Linux/Windows/macOS) and lets platform-conditional stdlib
# members (e.g. subprocess.CREATE_NO_WINDOW, ctypes.windll, pwd) resolve without per-OS type-ignores.
python-platform = "all"
[[tool.mypy.overrides]]
module = "test.*"
disallow_untyped_defs = false # untyped defs are OK in tests
[tool.ty.rules]
# Mirror mypy's ignore_missing_imports=true: optional extras (e.g. agno, google-genai) and
# platform-specific modules (e.g. AppKit on macOS) are not installed in the default dev environment,
# so we do not want unresolvable imports to fail the type check.
unresolved-import = "ignore"
possibly-missing-submodule = "ignore"
[tool.ty.src]
# NOTE: test/resources is intentionally NOT excluded here. This config is also discovered by the ty
# language server (used by the PYTHON_TY diagnostics provider) when it analyzes the sample fixture
# repos under test/resources; excluding them here would suppress the diagnostics those tests rely on.
# test/resources is instead excluded from the `ty check test` CLI task via its --exclude flag.
exclude = ["build/", "docs/"]
[[tool.ty.overrides]]
# Test code is heavily dynamic (pytest fixtures, MagicMock, intentionally loose Optionals). ty models
# pytest's fail/skip helpers and MagicMock far more strictly than mypy did (mypy inferred `Any` for
# these), which produces hundreds of diagnostics that are not real bugs. We therefore relax these
# specific type rules for tests only, while still catching genuinely broken test code (undefined
# names, syntax errors, unused type-ignore comments, etc.). The sample fixture repos under
# test/resources are excluded so the ty language server still reports real diagnostics on them.
include = ["test/**"]
exclude = ["test/resources/"]
[tool.ty.overrides.rules]
invalid-argument-type = "ignore"
invalid-key = "ignore"
invalid-return-type = "ignore"
no-matching-overload = "ignore"
not-subscriptable = "ignore"
parameter-already-assigned = "ignore"
too-many-positional-arguments = "ignore"
unresolved-attribute = "ignore"
unsupported-operator = "ignore"
[tool.poe.env]
PYDEVD_DISABLE_FILE_VALIDATION = "1"
@@ -152,9 +170,9 @@ _ruff_fix = "ruff check --fix src scripts test"
_ruff_format = "ruff format src scripts test"
lint = ["_ruff_format_check", "_ruff_check"]
format = ["_ruff_fix", "_ruff_format"]
_mypy_core = "mypy src/serena src/solidlsp"
_mypy_test = "mypy --disable-error-code no-untyped-def test"
type-check = ["_mypy_core", "_mypy_test"]
_ty_core = "ty check src/serena src/solidlsp"
_ty_test = "ty check test --exclude test/resources"
type-check = ["_ty_core", "_ty_test"]
# docs
_autogen_docs = "python docs/autogen_docs.py"
_sphinx_build = "sphinx-build -b html docs docs/_build -W --keep-going"
+1 -1
View File
@@ -1363,7 +1363,7 @@ class SerenaAgent:
self.issue_task(lambda: self.get_active_project_or_raise().remove_language(language), name=f"RemoveLanguage:{language.value}")
def get_tool(self, tool_class: type[TTool]) -> TTool:
return self._all_tools[tool_class] # type: ignore
return self._all_tools[tool_class]
def print_tool_overview(self) -> None:
ToolRegistry().print_tool_overview(self._active_tools.tools)
+3 -1
View File
@@ -789,7 +789,9 @@ class SerenaDashboardAPI:
# patch flask.cli.show_server to avoid printing the server info
from flask import cli
cli.show_server_banner = lambda *args, **kwargs: None
# ty cannot model reassigning a third-party module's function attribute (it rejects any
# replacement, even one with an identical signature), so the monkeypatch is suppressed here
cli.show_server_banner = lambda *args, **kwargs: None # ty: ignore[invalid-assignment]
self._app.run(host=self._host, port=port, debug=False, use_reloader=False, threaded=True)
return port
+2 -3
View File
@@ -1,4 +1,3 @@
# mypy: ignore-errors
import logging
import queue
import sys
@@ -109,7 +108,7 @@ class GuiLogViewer:
if self.menubar is not None:
dashboard_menu = tk.Menu(self.menubar, tearoff=0)
dashboard_menu.add_command(label="Copy URL", command=copy_url) # type: ignore
dashboard_menu.add_command(label="Copy URL", command=copy_url)
self.menubar.add_cascade(label="Dashboard", menu=dashboard_menu)
def add_log(self, message):
@@ -303,7 +302,7 @@ class GuiLogViewer:
if self.mode == "dashboard":
self.menubar = tk.Menu(self.root)
server_menu = tk.Menu(self.menubar, tearoff=0)
server_menu.add_command(label="Shutdown", command=self._shutdown_server) # type: ignore
server_menu.add_command(label="Shutdown", command=self._shutdown_server)
self.menubar.add_cascade(label="Server", menu=server_menu)
self.root.config(menu=self.menubar)
@@ -225,7 +225,7 @@ class JetBrainsPluginClient(ToStringMixin):
return ["_port", "project_root", "_plugin_version"]
@classmethod
def from_project(cls, project: Project, log_warning: bool = True) -> Self:
def from_project(cls, project: Project, log_warning: bool = True) -> "JetBrainsPluginClient":
resolved_path = Path(project.project_root).resolve()
if cls._last_port is not None:
@@ -355,7 +355,7 @@ class JetBrainsPluginClient(ToStringMixin):
"""
to_snake_case = lambda s: "".join(["_" + c.lower() if c.isupper() else c for c in s])
def convert(x): # type: ignore
def convert(x):
if isinstance(x, dict):
return {to_snake_case(k): convert(v) for k, v in x.items()}
elif isinstance(x, list):
@@ -721,5 +721,5 @@ class JetBrainsPluginClient(ToStringMixin):
def __enter__(self) -> Self:
return self
def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
+3 -3
View File
@@ -34,7 +34,7 @@ from serena.util.logging import MemoryLogHandler
log = logging.getLogger(__name__)
def configure_logging(*args, **kwargs) -> None: # type: ignore
def configure_logging(*args, **kwargs) -> None:
# We only do something here if logging has not yet been configured.
# Normally, logging is configured in the MCP server startup script.
if not logging.is_enabled():
@@ -95,7 +95,7 @@ class SerenaFastMCPTool(FastMCPTool):
param_desc = f"{param_doc.description.strip().strip('.') + '.'}"
properties["description"] = param_desc[0].upper() + param_desc[1:]
def execute_fn(**kwargs) -> str: # type: ignore
def execute_fn(**kwargs) -> str:
try:
return tool.apply_ex(log_call=True, catch_exceptions=False, **kwargs)
except ToolCallError as e:
@@ -182,7 +182,7 @@ class SerenaMCPFactory:
"""
s = deepcopy(schema)
def walk(node): # type: ignore
def walk(node):
if not isinstance(node, dict):
# lists get handled by parent calls
return node
+3 -1
View File
@@ -107,7 +107,9 @@ class ProjectServer:
from flask import cli
# suppress the default Flask startup banner
cli.show_server_banner = lambda *args, **kwargs: None
# ty cannot model reassigning a third-party module's function attribute (it rejects any
# replacement, even one with an identical signature), so the monkeypatch is suppressed here
cli.show_server_banner = lambda *args, **kwargs: None # ty: ignore[invalid-assignment]
self._app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
return port
+1 -1
View File
@@ -1321,7 +1321,7 @@ class SymbolDictGrouper(Generic[TSymbolDict], ABC):
"""
# avoid side effects by working on a deep-copy
symbols_copy = copy.deepcopy(symbols)
return self._group_by(symbols_copy, self._group_keys, self._group_children_keys, is_children=False) # type: ignore
return self._group_by(symbols_copy, self._group_keys, self._group_children_keys, is_children=False)
class LanguageServerSymbolDictGrouper(SymbolDictGrouper[LanguageServerSymbol.OutputDict]):
+1 -1
View File
@@ -200,7 +200,7 @@ class TaskExecutor:
self._task_executor_task_index += 1
else:
task_prefix_name = "BackgroundTask"
task_name = f"{task_prefix_name}:{name or task.__name__}"
task_name = f"{task_prefix_name}:{name or getattr(task, '__name__', 'task')}"
if logged:
log.info(f"Scheduling {task_name}")
task_obj = self.Task(function=task, name=task_name, logged=logged, timeout=timeout)
+1 -1
View File
@@ -48,5 +48,5 @@ class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit):
)
result = execute_shell_command(command, cwd=_cwd, capture_stderr=capture_stderr)
result = result.json()
result = result.model_dump_json()
return self._limit_length(result, max_answer_chars)
+1 -1
View File
@@ -62,7 +62,7 @@ class QueryProjectTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveP
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
return tool.apply(**json.loads(tool_params_json))
def _is_project_server_required(self, tool: Tool) -> bool:
match self.agent.get_language_backend():
+3 -3
View File
@@ -228,7 +228,7 @@ class FindSymbolTool(Tool, ToolMarkerSymbolicRead):
# In python 3.15 we could specify extra_items=True in the TypedDict definition,
# https://peps.python.org/pep-0728/
# If we ever upgrade to 3.15, we can remove the type: ignore[typeddict-unknown-key]
s_dict["info"] = symbol_info # type: ignore[typeddict-unknown-key]
s_dict["info"] = symbol_info
grouped_symbol_dicts = self.symbol_dict_grouper.group(symbol_dicts)
result = self._to_json(grouped_symbol_dicts)
@@ -306,12 +306,12 @@ class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead):
}
)
result = self.symbol_dict_grouper.group(reference_dicts) # type: ignore
result = self.symbol_dict_grouper.group(reference_dicts)
# shortened result closures, from least to most aggressive shortening
def make_refs_without_context() -> str:
"""References with name_path and reference line, without surrounding code lines"""
grouped = self.symbol_dict_grouper.group(copy.deepcopy(ref_summaries)) # type: ignore
grouped = self.symbol_dict_grouper.group(copy.deepcopy(ref_summaries))
return f"References without surrounding lines:\n{self._to_json(grouped)}"
def make_per_file_counts() -> str:
+2 -2
View File
@@ -327,7 +327,7 @@ class Tool(Component):
"""
return {}
def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, mcp_ctx: Context | None = None, **kwargs) -> str: # type: ignore
def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, mcp_ctx: Context | None = None, **kwargs) -> str:
"""
Applies the tool with logging and exception handling, using the given keyword arguments.
This method either returns a string result or raises a ToolCallError in case of an error during tool application
@@ -468,7 +468,7 @@ class EditingToolWithDiagnostics(Tool, ToolMarkerCanEdit):
def __enter__(self) -> Self:
return self
def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def format_result(
+1 -1
View File
@@ -19,7 +19,7 @@ def is_headless_environment() -> bool:
return False
# Check for DISPLAY variable (required for X11)
if not os.environ.get("DISPLAY"): # type: ignore
if not os.environ.get("DISPLAY"):
return True
# Check for SSH session
+1 -1
View File
@@ -145,7 +145,7 @@ class SuspendedLoggersContext:
root_logger.handlers.clear()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
root_logger = lg.getLogger()
root_logger.handlers = self.saved_root_handlers
if self.saved_root_level is not None:
+1 -1
View File
@@ -47,7 +47,7 @@ def subprocess_check_output(
) -> str:
output = subprocess.check_output(
args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), cwd=cwd, **subprocess_kwargs()
).decode(encoding) # type: ignore
).decode(encoding)
if strip:
output = output.strip()
return output
@@ -1038,7 +1038,7 @@ class ALLanguageServer(SolidLanguageServer):
if original_name != normalized_name and self._AL_OBJECT_NAME_PATTERN.match(original_name):
sel_range = symbol.get("selectionRange")
if sel_range:
start = sel_range.get("start") # type: ignore
start = sel_range.get("start")
if start and "line" in start and "character" in start:
line = start["line"]
char = start["character"]
@@ -82,7 +82,7 @@ class ClangdLanguageServer(SolidLanguageServer):
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
cache_format_version = 1
cpp_settings: dict[str, Any] = self._custom_settings or {}
cpp_settings = self._custom_settings or {}
return (
cache_format_version,
cpp_settings.get("clangd_version"),
@@ -179,7 +179,7 @@ class ClangdLanguageServer(SolidLanguageServer):
return None
# Get the target directory from ls_specific_settings, default to .serena
cpp_settings: dict[str, Any] = self._custom_settings or {}
cpp_settings = self._custom_settings or {}
compile_commands_rel_dir = cpp_settings.get("compile_commands_dir", ".serena")
compile_commands_dir = os.path.join(self.repository_root_path, compile_commands_rel_dir)
os.makedirs(compile_commands_dir, exist_ok=True)
@@ -411,9 +411,9 @@ class ClangdLanguageServer(SolidLanguageServer):
text_document_sync = capabilities["textDocumentSync"]
if isinstance(text_document_sync, int):
assert text_document_sync == 2 # type: ignore
assert text_document_sync == 2
else:
assert text_document_sync["change"] == 2 # type: ignore
assert text_document_sync["change"] == 2
assert "completionProvider" in capabilities
completion_provider = capabilities["completionProvider"]
+1 -1
View File
@@ -318,7 +318,7 @@ class ClojureLSP(SolidLanguageServer):
if source_paths is not None:
initialization_options["source-paths"] = source_paths
result = { # type: ignore
result = {
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
+2 -2
View File
@@ -106,7 +106,7 @@ class RuntimeDependencyCollection:
if not PlatformUtils.get_platform_id().is_windows():
import pwd
kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name # type: ignore
kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name
command = subprocess_util.convert_shell_cmd(command)
log.info("Running command %s in '%s'", f"'{command}'" if isinstance(command, str) else command, cwd)
@@ -120,7 +120,7 @@ class RuntimeDependencyCollection:
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
**kwargs,
) # type: ignore
)
if completed_process.returncode != 0:
log.warning("Command '%s' failed with return code %d", command, completed_process.returncode)
log.warning("Command output:\n%s", completed_process.stdout)
@@ -277,7 +277,7 @@ class CSharpLanguageServer(SolidLanguageServer):
if original_name != normalized_name:
sel_range = symbol.get("selectionRange")
if sel_range:
start = sel_range.get("start") # type: ignore
start = sel_range.get("start")
if start and "line" in start and "character" in start:
line = start["line"]
char = start["character"]
@@ -1299,26 +1299,26 @@ class EclipseJDTLS(SolidLanguageServer):
],
}
initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri] # type: ignore
initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri]
# IntelliCode bundle: only attached in default vscode-java VSIX mode.
# In upstream-jdtls mode (jdtls_path set) we don't ship IntelliCode — agentic Serena workflows
# don't use completion ranking, so the bundle would be inert dead weight.
if self.runtime_dependency_paths.intellicode_jar_path is not None:
initialize_params["initializationOptions"]["bundles"] = [self.runtime_dependency_paths.intellicode_jar_path] # type: ignore
initialize_params["initializationOptions"]["bundles"] = [self.runtime_dependency_paths.intellicode_jar_path]
else:
initialize_params["initializationOptions"]["bundles"] = [] # type: ignore
initialize_params["initializationOptions"]["bundles"] = []
initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [ # type: ignore
initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [
{"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True}
]
for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: # type: ignore
for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]:
assert "name" in runtime
assert "path" in runtime
assert os.path.exists(runtime["path"]), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist"
gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"] # type: ignore
gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"]
# In upstream-jdtls mode we don't ship a Gradle distribution — Buildship will use the project's
# ./gradlew wrapper or a system-installed Gradle via its standard discovery rules.
if self.runtime_dependency_paths.gradle_path is not None:
@@ -1483,7 +1483,7 @@ class EclipseJDTLS(SolidLanguageServer):
symbol["name"] = symbol["name"][: symbol["name"].index("(")]
children = symbol.get("children")
if children:
for child in children: # type: ignore
for child in children:
fix_name(child)
for root_symbol in result:
@@ -212,6 +212,7 @@ class ElixirTools(SolidLanguageServer):
)
self.server_ready = threading.Event()
self.request_id = 0
self._building_project = False
# Set generous timeout for Expert which can be slow to initialize and respond
self.set_request_timeout(180.0)
@@ -299,8 +299,8 @@ class FortranLanguageServer(SolidLanguageServer):
with self.open_file(relative_file_path):
self.server.notify.did_save_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: uri,
}
}
@@ -80,7 +80,7 @@ class GodotLanguageServer(SolidLanguageServer):
process_launch_info: ProcessLaunchInfo,
logging_fn: Callable[[str, str, StringDict | str], None] | None,
) -> LanguageServerInterface:
settings: dict = self._custom_settings or {}
settings = self._custom_settings or {}
port = settings.get("port", DEFAULT_GODOT_LS_PORT)
request_timeout = settings.get("request_timeout", DEFAULT_GODOT_REQUEST_TIMEOUT)
self._configured_request_timeout = settings.get("request_timeout")
@@ -141,7 +141,7 @@ class JuliaLanguageServer(SolidLanguageServer):
Returns the initialize params for the Julia Language Server.
"""
root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params: InitializeParams = { # type: ignore
initialize_params: InitializeParams = {
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
@@ -171,7 +171,7 @@ class JuliaLanguageServer(SolidLanguageServer):
}
],
}
return initialize_params # type: ignore
return initialize_params
def _start_server(self) -> None:
"""Start the LanguageServer.jl server process."""
+1 -1
View File
@@ -201,7 +201,7 @@ class Marksman(SolidLanguageServer):
Returns the initialize params for the Marksman Language Server.
"""
root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params: InitializeParams = { # type: ignore
initialize_params: InitializeParams = {
"processId": os.getpid(),
"locale": "en",
"rootPath": repository_absolute_path,
@@ -412,8 +412,8 @@ class PowerShellLanguageServer(SolidLanguageServer):
with self.open_file(relative_file_path):
self.server.notify.did_save_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: uri,
}
}
@@ -65,7 +65,7 @@ class PyrightServer(SolidLanguageServer):
Returns the initialize params for the Pyright Language Server.
"""
# Create basic initialization parameters
initialize_params = { # type: ignore
initialize_params = {
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": pathlib.Path(repository_absolute_path).as_uri(),
@@ -168,9 +168,10 @@ class PyrightServer(SolidLanguageServer):
message_text = ""
percentage: object | None = None
if isinstance(params, dict):
raw_message = params.get("message")
params_dict = cast("dict[str, object]", params)
raw_message = params_dict.get("message")
message_text = "" if raw_message is None else str(raw_message)
percentage = params.get("percentage")
percentage = params_dict.get("percentage")
elif params is not None:
message_text = str(params)
@@ -3,6 +3,7 @@
import logging
import os
import shutil
from typing import cast
from overrides import override
@@ -62,7 +63,7 @@ class RegalLanguageServer(SolidLanguageServer):
:return: LSP initialization parameters
"""
root_uri = PathUtils.path_to_uri(repository_absolute_path)
return {
initialize_params = {
"processId": os.getpid(),
"locale": "en",
"rootPath": repository_absolute_path,
@@ -76,9 +77,9 @@ class RegalLanguageServer(SolidLanguageServer):
"documentSymbol": {
"dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True,
"symbolKind": {"valueSet": list(range(1, 27))}, # type: ignore[arg-type]
"symbolKind": {"valueSet": list(range(1, 27))},
},
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, # type: ignore[list-item]
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
"codeAction": {"dynamicRegistration": True},
"formatting": {"dynamicRegistration": True},
},
@@ -95,17 +96,18 @@ class RegalLanguageServer(SolidLanguageServer):
}
],
}
return cast(InitializeParams, initialize_params)
def _start_server(self) -> None:
"""Start Regal language server process and wait for initialization."""
def register_capability_handler(params) -> None: # type: ignore[no-untyped-def]
def register_capability_handler(params) -> None:
return
def window_log_message(msg) -> None: # type: ignore[no-untyped-def]
def window_log_message(msg) -> None:
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params) -> None: # type: ignore[no-untyped-def]
def do_nothing(params) -> None:
return
self.server.on_request("client/registerCapability", register_capability_handler)
+4 -4
View File
@@ -133,7 +133,7 @@ class Solargraph(SolidLanguageServer):
if bundle_cmd.startswith("bin/"):
bundle_full_path = os.path.join(repository_root_path, bundle_cmd)
else:
bundle_full_path = find_executable_with_extensions(bundle_cmd) # type: ignore[assignment]
bundle_full_path = find_executable_with_extensions(bundle_cmd)
if bundle_full_path and os.path.exists(bundle_full_path):
bundle_path = bundle_full_path if bundle_cmd.startswith("bin/") else bundle_cmd
break
@@ -275,12 +275,12 @@ class Solargraph(SolidLanguageServer):
root_uri = pathlib.Path(repository_absolute_path).as_uri()
exclude_patterns = Solargraph._get_ruby_exclude_patterns(repository_absolute_path)
initialize_params: InitializeParams = { # type: ignore
initialize_params: InitializeParams = {
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"initializationOptions": {
"exclude": exclude_patterns, # type: ignore[dict-item]
"exclude": exclude_patterns,
},
"capabilities": {
"workspace": {
@@ -301,7 +301,7 @@ class Solargraph(SolidLanguageServer):
}
],
}
return initialize_params # type: ignore[return-value]
return initialize_params
def _start_server(self) -> None:
"""
@@ -313,8 +313,8 @@ class SolidityLanguageServer(SolidLanguageServer):
}
self.server.notify.did_open_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: uri,
LSPConstants.LANGUAGE_ID: self.language_id,
LSPConstants.VERSION: 0,
@@ -324,12 +324,12 @@ class SolidityLanguageServer(SolidLanguageServer):
)
try:
self.server.notify.did_change_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: uri,
LSPConstants.VERSION: 1,
},
LSPConstants.CONTENT_CHANGES: [ # type: ignore
LSPConstants.CONTENT_CHANGES: [
{
LSPConstants.RANGE: {
"start": {"line": 0, "character": 0},
@@ -348,8 +348,8 @@ class SolidityLanguageServer(SolidLanguageServer):
)
finally:
self.server.notify.did_close_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: uri,
}
}
@@ -438,7 +438,7 @@ class SvelteLanguageServer(SolidLanguageServer):
return
_orig_notify_send("$/onDidChangeTsOrJsFile", {"uri": uri, "changes": changes})
self.server.notify.send_notification = send_notification_wrapped # type: ignore[method-assign] # type: ignore[method-assign] # type: ignore[method-assign]
self.server.notify.send_notification = send_notification_wrapped
def _get_initialize_params(self) -> InitializeParams:
"""
@@ -36,7 +36,7 @@ if os.name != "nt": # Unix-like systems
import pwd
else:
# Dummy pwd module for Windows
class pwd: # type: ignore
class pwd:
@staticmethod
def getpwuid(uid: Any) -> Any:
return type("obj", (), {"pw_name": os.environ.get("USERNAME", "unknown")})()
@@ -344,7 +344,7 @@ class VueLanguageServer(SolidLanguageServer):
continue
new_item: dict = {}
new_item.update(item) # type: ignore[arg-type]
new_item.update(item)
new_item["absolutePath"] = str(abs_path)
new_item["relativePath"] = str(rel_path)
result.append(ls_types.Location(**new_item)) # type: ignore
@@ -390,7 +390,7 @@ class VueLanguageServer(SolidLanguageServer):
log.debug(f"Skipping invalid location item: {item}")
continue
abs_path = PathUtils.uri_to_path(item["uri"]) # type: ignore[arg-type]
abs_path = PathUtils.uri_to_path(item["uri"])
if not Path(abs_path).is_relative_to(self.repository_root_path):
log.warning(f"Found file reference outside repository: {abs_path}, skipping")
continue
@@ -401,7 +401,7 @@ class VueLanguageServer(SolidLanguageServer):
continue
new_item: dict = {}
new_item.update(item) # type: ignore[arg-type]
new_item.update(item)
new_item["absolutePath"] = str(abs_path)
new_item["relativePath"] = str(rel_path)
ret.append(Location(**new_item)) # type: ignore
+27 -27
View File
@@ -108,8 +108,8 @@ class LSPFileBuffer:
return
self._is_open_in_ls = True
self.language_server.server.notify.did_open_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: self.uri,
LSPConstants.LANGUAGE_ID: self.language_id,
LSPConstants.VERSION: 0,
@@ -121,8 +121,8 @@ class LSPFileBuffer:
def close(self) -> None:
if self._is_open_in_ls:
self.language_server.server.notify.did_close_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: self.uri,
}
}
@@ -223,10 +223,10 @@ class SymbolBodyFactory:
return existing_body
assert "location" in symbol
start_line = symbol["location"]["range"]["start"]["line"] # type: ignore
end_line = symbol["location"]["range"]["end"]["line"] # type: ignore
start_col = symbol["location"]["range"]["start"]["character"] # type: ignore
end_col = symbol["location"]["range"]["end"]["character"] # type: ignore
start_line = symbol["location"]["range"]["start"]["line"]
end_line = symbol["location"]["range"]["end"]["line"]
start_col = symbol["location"]["range"]["start"]["character"]
end_col = symbol["location"]["range"]["end"]["character"]
return SymbolBody(self._lines, start_line, start_col, end_line, end_col)
@@ -508,7 +508,7 @@ class SolidLanguageServer(ABC):
# For now, we assume that all language server implementations have the same signature of the constructor
# (which, unfortunately, differs from the signature of the base class).
# If this assumption is ever violated, we need branching logic here.
ls = ls_class(config, repository_root_path, solidlsp_settings) # type: ignore
ls = ls_class(config, repository_root_path, solidlsp_settings)
ls.set_request_timeout(timeout)
return ls
@@ -590,7 +590,7 @@ class SolidLanguageServer(ABC):
log.debug(f"LSP: {source} -> {target}: {msg!s}")
else:
logging_fn = None # type: ignore
logging_fn = None
# create the LanguageServerHandler, which provides the functionality to start the language server and communicate with it,
# preparing the launch command beforehand
@@ -1024,8 +1024,8 @@ class SolidLanguageServer(ABC):
if self._supports_pull_diagnostics():
try:
response = self.server.send.text_document_diagnostic(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: uri,
}
}
@@ -1380,8 +1380,8 @@ class SolidLanguageServer(ABC):
new_contents, new_l, new_c = TextUtils.insert_text_at_position(file_buffer.contents, line, column, text_to_be_inserted)
file_buffer.contents = new_contents
self.server.notify.did_change_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.VERSION: file_buffer.version,
LSPConstants.URI: file_buffer.uri,
},
@@ -1423,8 +1423,8 @@ class SolidLanguageServer(ABC):
)
file_buffer.contents = new_contents
self.server.notify.did_change_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.VERSION: file_buffer.version,
LSPConstants.URI: file_buffer.uri,
},
@@ -1766,15 +1766,15 @@ class SolidLanguageServer(ABC):
response = self.server.send.completion(completion_params)
if isinstance(response, list):
response = {"items": response, "isIncomplete": False}
if response is None or not response["isIncomplete"]: # type: ignore
if response is None or not response["isIncomplete"]:
break
# TODO: Understand how to appropriately handle `isIncomplete`
if response is None or (response["isIncomplete"] and not allow_incomplete): # type: ignore
if response is None or (response["isIncomplete"] and not allow_incomplete):
return []
if "items" in response:
response = response["items"] # type: ignore
response = response["items"]
response = cast(list[LSPTypes.CompletionItem], response)
@@ -1792,8 +1792,8 @@ class SolidLanguageServer(ABC):
if "label" in item:
completion_item["completionText"] = item["label"]
completion_item["kind"] = item["kind"] # type: ignore
elif "insertText" in item: # type: ignore
completion_item["kind"] = item["kind"]
elif "insertText" in item:
completion_item["completionText"] = item["insertText"]
completion_item["kind"] = item["kind"]
elif "textEdit" in item and "newText" in item["textEdit"]:
@@ -1977,9 +1977,9 @@ class SolidLanguageServer(ABC):
item["location"] = tree_location
location = item["location"]
if "absolutePath" not in location:
location["absolutePath"] = absolute_path # type: ignore
location["absolutePath"] = absolute_path
if "relativePath" not in location:
location["relativePath"] = relative_file_path # type: ignore
location["relativePath"] = relative_file_path
item["body"] = self.create_symbol_body(item, factory=body_factory)
@@ -2021,7 +2021,7 @@ class SolidLanguageServer(ABC):
if "children" in usymbol:
usymbol["children"] = convert_symbols_with_common_parent(usymbol["children"], usymbol) # type: ignore
else:
usymbol["children"] = [] # type: ignore
usymbol["children"] = []
unified_symbols.append(usymbol)
return unified_symbols
@@ -2080,7 +2080,7 @@ class SolidLanguageServer(ABC):
return []
# Create package symbol for directory
package_symbol = ls_types.UnifiedSymbolInformation( # type: ignore
package_symbol = ls_types.UnifiedSymbolInformation(
name=os.path.basename(abs_dir_path),
kind=ls_types.SymbolKind.Package,
location=ls_types.Location(
@@ -2128,7 +2128,7 @@ class SolidLanguageServer(ABC):
# Create file symbol, link with children
file_range = self._get_range_from_file_content(file_data.contents)
file_symbol = ls_types.UnifiedSymbolInformation( # type: ignore
file_symbol = ls_types.UnifiedSymbolInformation(
name=os.path.splitext(contained_dir_or_file_name)[0],
kind=ls_types.SymbolKind.File,
range=file_range,
@@ -2980,7 +2980,7 @@ class SolidLanguageServer(ABC):
migrated_cache[new_cache_key] = (file_hash, root_symbols)
num_symbols_migrated += len(all_symbols)
log.info("Migrated %d document symbols from legacy cache", num_symbols_migrated)
self._raw_document_symbols_cache = migrated_cache # type: ignore
self._raw_document_symbols_cache = migrated_cache
self._raw_document_symbols_cache_is_modified = True
self._save_raw_document_symbols_cache()
legacy_cache_file.unlink()
+2 -2
View File
@@ -571,7 +571,7 @@ class Language(str, Enum):
return CSharpLanguageServer
case self.CSHARP_OMNISHARP:
from solidlsp.language_servers.omnisharp import OmniSharp # type: ignore[attr-defined]
from solidlsp.language_servers.omnisharp import OmniSharp
return OmniSharp
case self.TYPESCRIPT:
@@ -679,7 +679,7 @@ class Language(str, Enum):
return ZigLanguageServer
case self.NIX:
from solidlsp.language_servers.nixd_ls import NixLanguageServer # type: ignore
from solidlsp.language_servers.nixd_ls import NixLanguageServer
return NixLanguageServer
case self.LUA:
+20 -14
View File
@@ -10,7 +10,7 @@ from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from queue import Empty, Queue
from typing import IO, Any, AnyStr
from typing import IO, Any, AnyStr, cast
from sensai.util.string import ToStringMixin
@@ -462,24 +462,30 @@ class StdioLanguageServer(LanguageServerInterface):
log.info("Starting language server process via command: %s", self.process_launch_info.cmd)
kwargs = subprocess_util.subprocess_kwargs()
kwargs["start_new_session"] = self.start_independent_lsp_process
self.process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
env=child_proc_env,
cwd=self.process_launch_info.cwd,
shell=True,
**kwargs,
# the language server is launched with binary (bytes) pipes; the cast is needed because the
# presence of platform-specific **kwargs prevents ty from selecting the bytes Popen overload
process = cast(
"subprocess.Popen[bytes]",
subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
env=child_proc_env,
cwd=self.process_launch_info.cwd,
shell=True,
**kwargs,
),
)
self.process = process
# Check if process terminated immediately
if self.process.returncode is not None:
if process.returncode is not None:
log.error("Language server has already terminated/could not be started")
# Process has already terminated
stderr_data = self.process.stderr.read() if self.process.stderr else b""
stderr_data = process.stderr.read() if process.stderr else b""
error_message = stderr_data.decode("utf-8", errors="replace")
raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}")
raise RuntimeError(f"Process terminated immediately with code {process.returncode}. Error: {error_message}")
# start threads to read stdout and stderr of the process
threading.Thread(
@@ -522,7 +528,7 @@ class StdioLanguageServer(LanguageServerInterface):
except Exception:
pass
def _read_bytes_from_process(self, process, stream, num_bytes) -> bytes: # type: ignore
def _read_bytes_from_process(self, process, stream, num_bytes) -> bytes:
"""Read exactly num_bytes from process stdout"""
data = b""
while len(data) < num_bytes:
+1 -1
View File
@@ -527,7 +527,7 @@ class PlatformUtils:
_anonymous_ = ("u",)
sys_info = SYSTEM_INFO()
ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info)) # type: ignore
ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info))
arch_map = {
9: "AMD64",
+1 -1
View File
@@ -15,7 +15,7 @@ def subprocess_kwargs() -> dict:
"""
kwargs = {}
if platform.system() == "Windows":
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW # type: ignore
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
return kwargs
+1 -1
View File
@@ -115,7 +115,7 @@ class SafeZipExtractor:
"""
if sys.platform.startswith("win"):
return Path(rf"\\?\{os.path.abspath(path)}")
return path # type: ignore
return path
# Example usage:
+4 -4
View File
@@ -464,22 +464,22 @@ class TestToolUseCounter:
counter = ToolUseCounter(n_recent_grep_uses=2, n_recent_read_file_uses=1)
hook_stub = type("HookStub", (), {"session_persistence_dir": str(tmp_path)})()
counter.save(hook_stub) # type: ignore[arg-type]
loaded = ToolUseCounter.load(hook_stub) # type: ignore[arg-type]
counter.save(hook_stub)
loaded = ToolUseCounter.load(hook_stub)
assert loaded.n_recent_grep_uses == 2
assert loaded.n_recent_read_file_uses == 1
def test_load_returns_fresh_counter_on_missing_file(self, tmp_path: Path):
hook_stub = type("HookStub", (), {"session_persistence_dir": str(tmp_path / "nonexistent")})()
loaded = ToolUseCounter.load(hook_stub) # type: ignore[arg-type]
loaded = ToolUseCounter.load(hook_stub)
assert loaded == ToolUseCounter()
def test_load_returns_fresh_counter_on_corrupt_file(self, tmp_path: Path):
hook_stub = type("HookStub", (), {"session_persistence_dir": str(tmp_path)})()
path = tmp_path / ToolUseCounter._FILE_NAME
path.write_bytes(b"not a pickle")
loaded = ToolUseCounter.load(hook_stub) # type: ignore[arg-type]
loaded = ToolUseCounter.load(hook_stub)
assert loaded == ToolUseCounter()
def test_is_hook_active_respects_min_interval(self):
+1 -1
View File
@@ -25,7 +25,7 @@ class BaseMockTool(Tool):
"""A mock Tool class for testing."""
def __init__(self):
super().__init__(MockAgent()) # type: ignore
super().__init__(MockAgent())
class BasicTool(BaseMockTool):
+2 -2
View File
@@ -46,7 +46,7 @@ class BaseCase:
id: str
def to_pytest_param(self, *marks: MarkDecorator | Mark) -> ParameterSet:
return pytest.param(self.language, self, marks=[*get_pytest_markers(self.language), *marks], id=self.id) # type: ignore
return pytest.param(self.language, self, marks=[*get_pytest_markers(self.language), *marks], id=self.id)
@dataclass
@@ -1290,7 +1290,7 @@ class TestPromptProvision:
@classmethod
def _call_tool(cls, agent: SerenaAgent, tool_class: type[Tool], session_id: str = "global", **kwargs) -> str:
result = agent.get_tool(tool_class).apply_ex(mcp_ctx=cls.MockContext(session_id), **kwargs) # type: ignore
result = agent.get_tool(tool_class).apply_ex(mcp_ctx=cls.MockContext(session_id), **kwargs)
return result
@staticmethod
+4 -4
View File
@@ -251,10 +251,10 @@ class TestSymbolDictTypes:
:param key_type: the corresponding key type (Literal[...]) that the dict should have for keys
"""
dict_type_keys = dict_type.__annotations__.keys()
assert len(dict_type_keys) == len(
key_type.__args__ # type: ignore
), f"Expected {len(key_type.__args__)} keys in {dict_type}, but got {len(dict_type_keys)}" # type: ignore
for expected_key in key_type.__args__: # type: ignore
assert len(dict_type_keys) == len(key_type.__args__), (
f"Expected {len(key_type.__args__)} keys in {dict_type}, but got {len(dict_type_keys)}"
)
for expected_key in key_type.__args__:
assert expected_key in dict_type_keys, f"Expected key '{expected_key}' not found in {dict_type}"
def test_ls_symbol_dict_type(self):
@@ -265,7 +265,7 @@ class TestAngularCoreProbe:
from solidlsp.language_servers.angular_language_server import AngularLanguageServer
stub = type("Stub", (), {"repository_root_path": repo_path})()
return AngularLanguageServer._find_angular_core_install(stub) # type: ignore[arg-type]
return AngularLanguageServer._find_angular_core_install(stub)
def test_finds_core_at_project_root(self, tmp_path) -> None:
core_pkg = tmp_path / "node_modules" / "@angular" / "core" / "package.json"
@@ -1,4 +1,3 @@
# type: ignore
import os
import pytest
Generated
+26 -43
View File
@@ -1485,47 +1485,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820, upload-time = "2024-10-16T13:16:17.566Z" },
]
[[package]]
name = "mypy"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" },
{ url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" },
{ url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" },
{ url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" },
{ url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" },
{ url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" },
{ url = "https://files.pythonhosted.org/packages/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" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "myst-nb"
version = "1.4.0"
@@ -2890,7 +2849,6 @@ agno = [
dev = [
{ name = "jinja2" },
{ name = "jupyter-book" },
{ name = "mypy" },
{ name = "poethepoet" },
{ name = "pyasn1" },
{ name = "pyinstrument" },
@@ -2906,6 +2864,7 @@ dev = [
{ name = "syrupy" },
{ name = "toml-sort" },
{ name = "tornado" },
{ name = "ty" },
{ name = "types-pyyaml" },
{ name = "types-requests" },
{ name = "wheel" },
@@ -2931,7 +2890,6 @@ requires-dist = [
{ name = "jupyter-book", marker = "extra == 'dev'", specifier = "==1.0.4.post1" },
{ name = "lsprotocol", specifier = "==2025.0.0" },
{ name = "mcp", specifier = "==1.27.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.17.0" },
{ name = "oslex", specifier = "==2.0.0" },
{ name = "overrides", specifier = "==7.7.0" },
{ name = "pathspec", specifier = "==0.12.1" },
@@ -2967,6 +2925,7 @@ requires-dist = [
{ name = "toml-sort", marker = "extra == 'dev'", specifier = "==0.24.2" },
{ name = "tornado", marker = "extra == 'dev'", specifier = "==6.5.7" },
{ name = "tqdm", specifier = "==4.67.1" },
{ name = "ty", marker = "extra == 'dev'", specifier = "==0.0.24" },
{ 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" },
@@ -3593,6 +3552,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
]
[[package]]
name = "ty"
version = "0.0.24"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/96/652a425030f95dc2c9548d9019e52502e17079e1daeefbc4036f1c0905b4/ty-0.0.24.tar.gz", hash = "sha256:9fe42f6b98207bdaef51f71487d6d087f2cb02555ee3939884d779b2b3cc8bfc", size = 5354286, upload-time = "2026-03-19T16:55:57.035Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/e5/34457ee11708e734ba81ad65723af83030e484f961e281d57d1eecf08951/ty-0.0.24-py3-none-linux_armv6l.whl", hash = "sha256:1ab4f1f61334d533a3fdf5d9772b51b1300ac5da4f3cdb0be9657a3ccb2ce3e7", size = 10394877, upload-time = "2026-03-19T16:55:54.246Z" },
{ url = "https://files.pythonhosted.org/packages/44/81/bc9a1b1a87f43db15ab64ad781a4f999734ec3b470ad042624fa875b20e6/ty-0.0.24-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:facbf2c4aaa6985229e08f8f9bf152215eb078212f22b5c2411f35386688ab42", size = 10211109, upload-time = "2026-03-19T16:55:28.554Z" },
{ url = "https://files.pythonhosted.org/packages/e4/63/cfc805adeaa61d63ba3ea71127efa7d97c40ba36d97ee7bd957341d05107/ty-0.0.24-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b6d2a3b6d4470c483552a31e9b368c86f154dcc964bccb5406159dc9cd362246", size = 9694769, upload-time = "2026-03-19T16:55:34.309Z" },
{ url = "https://files.pythonhosted.org/packages/33/09/edc220726b6ec44a58900401f6b27140997ef15026b791e26b69a6e69eb5/ty-0.0.24-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c94c25d0500939fd5f8f16ce41cbed5b20528702c1d649bf80300253813f0a2", size = 10176287, upload-time = "2026-03-19T16:55:37.17Z" },
{ url = "https://files.pythonhosted.org/packages/f8/bf/cbe2227be711e65017655d8ee4d050f4c92b113fb4dc4c3bd6a19d3a86d8/ty-0.0.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:89cbe7bc7df0fab02dbd8cda79b737df83f1ef7fb573b08c0ee043dc68cffb08", size = 10214832, upload-time = "2026-03-19T16:56:08.518Z" },
{ url = "https://files.pythonhosted.org/packages/af/1d/d15803ee47e9143d10e10bd81ccc14761d08758082bda402950685f0ddfe/ty-0.0.24-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2c5d269bcc9b764850c99f457b5018a79b3ef40ecfbc03344e65effd6cf743", size = 10709892, upload-time = "2026-03-19T16:56:05.727Z" },
{ url = "https://files.pythonhosted.org/packages/36/12/6db0d86c477147f67b9052de209421d76c3e855197b000c25fcbbe86b3a2/ty-0.0.24-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba44512db5b97c3bbd59d93e11296e8548d0c9a3bdd1280de36d7ff22d351896", size = 11280872, upload-time = "2026-03-19T16:56:02.899Z" },
{ url = "https://files.pythonhosted.org/packages/1b/fc/155fe83a97c06d33ccc9e0f428258b32df2e08a428300c715d34757f0111/ty-0.0.24-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a52b7f589c3205512a9c50ba5b2b1e8c0698b72e51b8b9285c90420c06f1cae8", size = 11060520, upload-time = "2026-03-19T16:55:59.956Z" },
{ url = "https://files.pythonhosted.org/packages/ac/f1/32c05a1c4c3c2a95c5b7361dee03a9bf1231d4ad096b161c838b45bce5a0/ty-0.0.24-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7981df5c709c054da4ac5d7c93f8feb8f45e69e829e4461df4d5f0988fe67d04", size = 10791455, upload-time = "2026-03-19T16:55:25.728Z" },
{ url = "https://files.pythonhosted.org/packages/17/2c/53c1ea6bedfa4d4ab64d4de262d8f5e405ecbffefd364459c628c0310d33/ty-0.0.24-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2860151ad95a00d0f0280b8fef79900d08dcd63276b57e6e5774f2c055979c5", size = 10156708, upload-time = "2026-03-19T16:55:45.563Z" },
{ url = "https://files.pythonhosted.org/packages/45/39/7d2919cf194707169474d80720a5f3d793e983416f25e7ffcf80504c9df2/ty-0.0.24-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5674a1146d927ab77ff198a88e0c4505134ced342a0e7d1beb4a076a728b7496", size = 10236263, upload-time = "2026-03-19T16:55:31.474Z" },
{ url = "https://files.pythonhosted.org/packages/cf/7f/48eac722f2fd12a5b7aae0effdcb75c46053f94b783d989e3ef0d7380082/ty-0.0.24-py3-none-musllinux_1_2_i686.whl", hash = "sha256:438ecbf1608a9b16dd84502f3f1b23ef2ef32bbd0ab3e0ca5a82f0e0d1cd41ea", size = 10402559, upload-time = "2026-03-19T16:55:39.602Z" },
{ url = "https://files.pythonhosted.org/packages/75/e0/8cf868b9749ce1e5166462759545964e95b02353243594062b927d8bff2a/ty-0.0.24-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ddeed3098dd92a83964e7aa7b41e509ba3530eb539fc4cd8322ff64a09daf1f5", size = 10893684, upload-time = "2026-03-19T16:55:51.439Z" },
{ url = "https://files.pythonhosted.org/packages/17/9f/f54bf3be01d2c2ed731d10a5afa3324dc66f987a6ae0a4a6cbfa2323d080/ty-0.0.24-py3-none-win32.whl", hash = "sha256:83013fb3a4764a8f8bcc6ca11ff8bdfd8c5f719fc249241cb2b8916e80778eb1", size = 9781542, upload-time = "2026-03-19T16:56:11.588Z" },
{ url = "https://files.pythonhosted.org/packages/fb/49/c004c5cc258b10b3a145666e9a9c28ae7678bc958c8926e8078d5d769081/ty-0.0.24-py3-none-win_amd64.whl", hash = "sha256:748a60eb6912d1cf27aaab105ffadb6f4d2e458a3fcadfbd3cf26db0d8062eeb", size = 10764801, upload-time = "2026-03-19T16:55:42.752Z" },
{ url = "https://files.pythonhosted.org/packages/e2/59/006a074e185bfccf5e4c026015245ab4fcd2362b13a8d24cf37a277909a9/ty-0.0.24-py3-none-win_arm64.whl", hash = "sha256:280a3d31e86d0721947238f17030c33f0911cae851d108ea9f4e3ab12a5ed01f", size = 10194093, upload-time = "2026-03-19T16:55:48.303Z" },
]
[[package]]
name = "typer"
version = "0.24.1"