Skip to content

Commit 433d198

Browse files
committed
feat: add support for ty (Astral's Python type checker)
1 parent b4191c6 commit 433d198

File tree

4 files changed

+115
-0
lines changed

4 files changed

+115
-0
lines changed

container/registry.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ repo = "rust-lang/rust-analyzer"
1414
type = "pypi"
1515
package = "pyrefly"
1616

17+
[ty]
18+
type = "github"
19+
repo = "astral-sh/ty"
20+
strip_v = true
21+
1722
[deno]
1823
type = "github"
1924
repo = "denoland/deno"

container/ty/ContainerFile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# ty has built-in LSP
2+
ARG VERSION=0.0.5
3+
FROM ghcr.io/astral-sh/ty:${VERSION}
4+
ARG VERSION
5+
LABEL org.opencontainers.image.version="${VERSION}"
6+
7+
WORKDIR /workspace
8+
ENTRYPOINT ["ty", "server"]

src/lsp_client/clients/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
from .pyrefly import PyreflyClient
55
from .pyright import PyrightClient
66
from .rust_analyzer import RustAnalyzerClient
7+
from .ty import TyClient
78
from .typescript import TypescriptClient
89

910
PythonClient = PyrightClient
1011
RustClient = RustAnalyzerClient
1112
TypeScriptClient = TypescriptClient
1213
DenoLSPClient = DenoClient
14+
TyLSPClient = TyClient
1315

1416
__all__ = [
1517
"DenoLSPClient",
1618
"PyreflyClient",
1719
"PythonClient",
1820
"RustClient",
21+
"TyLSPClient",
1922
"TypeScriptClient",
2023
]

src/lsp_client/clients/ty.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from __future__ import annotations
2+
3+
import shutil
4+
import sys
5+
from functools import partial
6+
from subprocess import CalledProcessError
7+
from typing import Any, Literal, override
8+
9+
import anyio
10+
from attrs import define
11+
from loguru import logger
12+
13+
from lsp_client.capability.notification import (
14+
WithNotifyDidChangeConfiguration,
15+
)
16+
from lsp_client.capability.request import (
17+
WithRequestDefinition,
18+
WithRequestHover,
19+
WithRequestReferences,
20+
)
21+
from lsp_client.capability.server_notification import (
22+
WithReceiveLogMessage,
23+
WithReceivePublishDiagnostics,
24+
)
25+
from lsp_client.capability.server_request import (
26+
WithRespondConfigurationRequest,
27+
WithRespondWorkspaceFoldersRequest,
28+
)
29+
from lsp_client.client.abc import LSPClient
30+
from lsp_client.server.abc import LSPServer
31+
from lsp_client.server.container import ContainerServer
32+
from lsp_client.server.local import LocalServer
33+
from lsp_client.utils.types import lsp_type
34+
35+
TyContainerServer = partial(ContainerServer, image="ghcr.io/astral-sh/ty:latest")
36+
37+
38+
@define
39+
class TyClient(
40+
LSPClient,
41+
WithNotifyDidChangeConfiguration,
42+
WithRequestDefinition,
43+
WithRequestHover,
44+
WithRequestReferences,
45+
WithReceiveLogMessage,
46+
WithReceivePublishDiagnostics,
47+
WithRespondConfigurationRequest,
48+
WithRespondWorkspaceFoldersRequest,
49+
):
50+
"""
51+
- Language: Python
52+
- Homepage: https://docs.astral.sh/ty/
53+
- Github: https://github.com/astral-sh/ty
54+
"""
55+
56+
log_level: Literal["trace", "debug", "info", "warn", "error"] = "info"
57+
diagnostic_mode: Literal["openFilesOnly", "workspace"] = "openFilesOnly"
58+
inlay_hints_variable_types: bool = True
59+
inlay_hints_call_argument_names: bool = True
60+
61+
@override
62+
def get_language_id(self) -> lsp_type.LanguageKind:
63+
return lsp_type.LanguageKind.Python
64+
65+
@override
66+
def create_default_server(self) -> LSPServer:
67+
return LocalServer(command=["ty", "server"])
68+
69+
@override
70+
def create_initialization_options(self) -> dict[str, Any]:
71+
return {
72+
"logLevel": self.log_level,
73+
"diagnosticMode": self.diagnostic_mode,
74+
"inlayHints": {
75+
"variableTypes": self.inlay_hints_variable_types,
76+
"callArgumentNames": self.inlay_hints_call_argument_names,
77+
},
78+
}
79+
80+
@override
81+
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
82+
return
83+
84+
@override
85+
async def ensure_installed(self) -> None:
86+
if shutil.which("ty"):
87+
return
88+
89+
logger.warning("ty not found, attempting to install via pip...")
90+
91+
try:
92+
await anyio.run_process([sys.executable, "-m", "pip", "install", "ty"])
93+
logger.info("Successfully installed ty via pip")
94+
return
95+
except CalledProcessError as e:
96+
raise RuntimeError(
97+
"Could not install ty. Please install it manually with 'pip install ty' or 'uv tool install ty'. "
98+
"See https://docs.astral.sh/ty/installation/ for more information."
99+
) from e

0 commit comments

Comments
 (0)