Skip to content

Commit 6f5d2d3

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

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

container/registry.toml

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

17+
[ty]
18+
type = "pypi"
19+
package = "ty"
20+
1721
[deno]
1822
type = "github"
1923
repo = "denoland/deno"

container/ty/ContainerFile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Multi-stage build for ty
2+
ARG VERSION=latest
3+
FROM python:3.12-slim AS builder
4+
ARG VERSION
5+
RUN pip install --no-cache-dir ty==${VERSION}
6+
7+
FROM python:3.12-slim
8+
ARG VERSION
9+
LABEL org.opencontainers.image.version="${VERSION}"
10+
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
11+
COPY --from=builder /usr/local/bin/ty /usr/local/bin/ty
12+
13+
WORKDIR /workspace
14+
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: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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(
36+
ContainerServer, image="ghcr.io/observerw/lsp-client/ty:latest"
37+
)
38+
39+
40+
@define
41+
class TyClient(
42+
LSPClient,
43+
WithNotifyDidChangeConfiguration,
44+
WithRequestDefinition,
45+
WithRequestHover,
46+
WithRequestReferences,
47+
WithReceiveLogMessage,
48+
WithReceivePublishDiagnostics,
49+
WithRespondConfigurationRequest,
50+
WithRespondWorkspaceFoldersRequest,
51+
):
52+
"""
53+
- Language: Python
54+
- Homepage: https://docs.astral.sh/ty/
55+
- Github: https://github.com/astral-sh/ty
56+
"""
57+
58+
log_level: Literal["trace", "debug", "info", "warn", "error"] = "info"
59+
diagnostic_mode: Literal["openFilesOnly", "workspace"] = "openFilesOnly"
60+
inlay_hints_variable_types: bool = True
61+
inlay_hints_call_argument_names: bool = True
62+
63+
@override
64+
def get_language_id(self) -> lsp_type.LanguageKind:
65+
return lsp_type.LanguageKind.Python
66+
67+
@override
68+
def create_default_server(self) -> LSPServer:
69+
return LocalServer(command=["ty", "server"])
70+
71+
@override
72+
def create_initialization_options(self) -> dict[str, Any]:
73+
return {
74+
"logLevel": self.log_level,
75+
"diagnosticMode": self.diagnostic_mode,
76+
"inlayHints": {
77+
"variableTypes": self.inlay_hints_variable_types,
78+
"callArgumentNames": self.inlay_hints_call_argument_names,
79+
},
80+
}
81+
82+
@override
83+
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
84+
return
85+
86+
@override
87+
async def ensure_installed(self) -> None:
88+
if shutil.which("ty"):
89+
return
90+
91+
logger.warning("ty not found, attempting to install via pip...")
92+
93+
try:
94+
await anyio.run_process([sys.executable, "-m", "pip", "install", "ty"])
95+
logger.info("Successfully installed ty via pip")
96+
return
97+
except CalledProcessError as e:
98+
raise RuntimeError(
99+
"Could not install ty. Please install it manually with 'pip install ty' or 'uv tool install ty'. "
100+
"See https://docs.astral.sh/ty/installation/ for more information."
101+
) from e

0 commit comments

Comments
 (0)