Skip to content

Commit 8305f7e

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

File tree

8 files changed

+138
-174
lines changed

8 files changed

+138
-174
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: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
WithRequestDeclaration,
18+
WithRequestDefinition,
19+
WithRequestDocumentSymbol,
20+
WithRequestHover,
21+
WithRequestReferences,
22+
WithRequestTypeDefinition,
23+
WithRequestWorkspaceSymbol,
24+
)
25+
from lsp_client.capability.server_notification import (
26+
WithReceiveLogMessage,
27+
WithReceiveLogTrace,
28+
WithReceivePublishDiagnostics,
29+
WithReceiveShowMessage,
30+
)
31+
from lsp_client.capability.server_request import (
32+
WithRespondConfigurationRequest,
33+
WithRespondShowDocumentRequest,
34+
WithRespondShowMessageRequest,
35+
WithRespondWorkspaceFoldersRequest,
36+
)
37+
from lsp_client.client.abc import LSPClient
38+
from lsp_client.server.abc import LSPServer
39+
from lsp_client.server.container import ContainerServer
40+
from lsp_client.server.local import LocalServer
41+
from lsp_client.utils.types import lsp_type
42+
43+
TyContainerServer = partial(
44+
ContainerServer, image="ghcr.io/observerw/lsp-client/ty:latest"
45+
)
46+
47+
48+
@define
49+
class TyClient(
50+
LSPClient,
51+
WithNotifyDidChangeConfiguration,
52+
WithRequestDeclaration,
53+
WithRequestDefinition,
54+
WithRequestDocumentSymbol,
55+
WithRequestHover,
56+
WithRequestReferences,
57+
WithRequestTypeDefinition,
58+
WithRequestWorkspaceSymbol,
59+
WithReceiveLogMessage,
60+
WithReceiveLogTrace,
61+
WithReceivePublishDiagnostics,
62+
WithReceiveShowMessage,
63+
WithRespondConfigurationRequest,
64+
WithRespondShowDocumentRequest,
65+
WithRespondShowMessageRequest,
66+
WithRespondWorkspaceFoldersRequest,
67+
):
68+
"""
69+
- Language: Python
70+
- Homepage: https://docs.astral.sh/ty/
71+
- Github: https://github.com/astral-sh/ty
72+
"""
73+
74+
log_level: Literal["trace", "debug", "info", "warn", "error"] = "info"
75+
diagnostic_mode: Literal["openFilesOnly", "workspace"] = "openFilesOnly"
76+
inlay_hints_variable_types: bool = True
77+
inlay_hints_call_argument_names: bool = True
78+
79+
@override
80+
def get_language_id(self) -> lsp_type.LanguageKind:
81+
return lsp_type.LanguageKind.Python
82+
83+
@override
84+
def create_default_server(self) -> LSPServer:
85+
return LocalServer(command=["ty", "server"])
86+
87+
@override
88+
def create_initialization_options(self) -> dict[str, Any]:
89+
return {
90+
"logLevel": self.log_level,
91+
"diagnosticMode": self.diagnostic_mode,
92+
"inlayHints": {
93+
"variableTypes": self.inlay_hints_variable_types,
94+
"callArgumentNames": self.inlay_hints_call_argument_names,
95+
},
96+
}
97+
98+
@override
99+
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
100+
return
101+
102+
@override
103+
async def ensure_installed(self) -> None:
104+
if shutil.which("ty"):
105+
return
106+
107+
logger.warning("ty not found, attempting to install via pip...")
108+
109+
try:
110+
await anyio.run_process([sys.executable, "-m", "pip", "install", "ty"])
111+
logger.info("Successfully installed ty via pip")
112+
return
113+
except CalledProcessError as e:
114+
raise RuntimeError(
115+
"Could not install ty. Please install it manually with 'pip install ty' or 'uv tool install ty'. "
116+
"See https://docs.astral.sh/ty/installation/ for more information."
117+
) from e

src/lsp_client/server/container.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,5 @@ async def run_process(self, workspace: Workspace) -> AsyncGenerator[None]:
199199
logger.debug("Running docker runtime with command: {}", command)
200200

201201
self._local = LocalServer(command=command)
202-
async with self._local.run_process():
202+
async with self._local.run_process(workspace):
203203
yield

src/lsp_client/server/docker.py

Lines changed: 0 additions & 171 deletions
This file was deleted.

src/lsp_client/server/local.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from lsp_client.jsonrpc.parse import read_raw_package, write_raw_package
1616
from lsp_client.jsonrpc.types import RawPackage
1717
from lsp_client.server.abc import LSPServer
18+
from lsp_client.utils.workspace import Workspace
1819

1920

2021
@final
@@ -69,7 +70,7 @@ async def kill(self) -> None:
6970

7071
@override
7172
@asynccontextmanager
72-
async def run_process(self) -> AsyncGenerator[None]:
73+
async def run_process(self, workspace: Workspace) -> AsyncGenerator[None]:
7374
command = [*self.command, *self.args]
7475
logger.debug("Running with command: {}", command)
7576

src/lsp_client/utils/inspect.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from lsp_client.client.abc import LSPClient
1818
from lsp_client.jsonrpc.convert import lsp_type, request_serialize, response_deserialize
1919
from lsp_client.server.abc import LSPServer
20+
from lsp_client.utils.workspace import DEFAULT_WORKSPACE
2021

2122

2223
@frozen
@@ -32,7 +33,7 @@ async def inspect_capabilities(
3233
if not __debug__:
3334
raise RuntimeError("inspect_capabilities can only be used in debug mode")
3435

35-
async with server.serve():
36+
async with server.serve(DEFAULT_WORKSPACE):
3637
# send a fake initialize request to get server capabilities
3738
req = lsp_type.InitializeRequest(
3839
id="initialize",

0 commit comments

Comments
 (0)