Skip to content

Commit 0ee655a

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

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-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", "lsp"]

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

0 commit comments

Comments
 (0)