Skip to content

Commit 758a2a6

Browse files
authored
feat: add support for ty (Astral's Python type checker) (#8)
* feat: add support for ty (Astral's Python type checker) * fix: remove unused export
1 parent b4191c6 commit 758a2a6

File tree

8 files changed

+133
-177
lines changed

8 files changed

+133
-177
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: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
from .deno import DenoClient
43
from .pyrefly import PyreflyClient
54
from .pyright import PyrightClient
65
from .rust_analyzer import RustAnalyzerClient
@@ -9,10 +8,8 @@
98
PythonClient = PyrightClient
109
RustClient = RustAnalyzerClient
1110
TypeScriptClient = TypescriptClient
12-
DenoLSPClient = DenoClient
1311

1412
__all__ = [
15-
"DenoLSPClient",
1613
"PyreflyClient",
1714
"PythonClient",
1815
"RustClient",

src/lsp_client/clients/ty.py

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