How To Migrate to v2.0¶
The highlight of the pygls v2 release is upgrading lsprotocol to v2025.x bringing with it support for the proposed LSP v3.18 types and methods.
The new version includes standardised object names (so no more classes like NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType!)
With the major version bump, this release also takes the opportunity to clean up the codebase by removing deprecated code and renaming a few things to try and improve overall consistency. This guide outlines how to adapt an existing server to the breaking changes introduced in this release.
Known Migrations
Finished your migration?
Have you migrated your server to v2?
Feel free to open a pull request and add yours to the list below!
You may find these projects that have already successfully migrated to v2 a useful reference:
Python Support¶
pygls v2
Removes support for Python 3.8
Adds support for Python 3.13 and 3.14 (with the GIL, you are welcome to try a free-threaded build just note that it has not been tested yet!)
URI Handling¶
The pygls.uris.to_fs_path() will now return None for URIs that do not have a file: scheme.
Removed Deprecated Functions¶
The following methods and functions have been deprecated for some time and have now been removed in pygls v2.
pygls v1 |
pygls v2 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Sending Custom Notifications¶
The send_notification method on the LanguageServer has been removed without a prior deprecation notice (sorry!)
To send custom notifications in v2, use the notify method on the underlying protocol object.
# Before
server.send_notification("my/customNotification", {"example": "data"})
# After
server.protocol.notify("my/customNotification", {"example": "data"})
See How To Send Custom Messages for more details
Server commands are now called with individual arguments and can use type annotations¶
Instead of calling sever commands with a single arguments of type list containing all the command arguments, pygls v2 now unpacks the arguments and passes them as individual parameters to the command method.
pygls will now inspect a function’s type annotations when handling workspace/executeCommand requests, automatically converting JSON values to attrs class instances, or responding with an error if appropriate.
It is not mandatory to start using type annotations in your command definitions, but you will notice a difference in how pygls calls your server command methods if they take arguments.
Before
@server.command("codeLens.evaluateSum")
def evaluate_sum(ls: LanguageServer, args):
logging.info("arguments: %s", args) # here args is a list of dict
arguments = args[0]
document = ls.workspace.get_text_document(arguments["uri"])
line = document.lines[arguments["line"]]
# Compute the edit that will update the document with the result.
answer = arguments["left"] + arguments["right"]
edit = types.TextDocumentEdit(
text_document=types.OptionalVersionedTextDocumentIdentifier(
uri=arguments["uri"],
version=document.version,
),
edits=[
types.TextEdit(
new_text=f"{line.strip()} {answer}\n",
range=types.Range(
start=types.Position(line=arguments["line"], character=0),
end=types.Position(line=arguments["line"] + 1, character=0),
),
)
],
)
# Apply the edit.
ls.workspace_apply_edit(
types.ApplyWorkspaceEditParams(
edit=types.WorkspaceEdit(document_changes=[edit]),
),
)
After
@attrs.define
class EvaluateSumArgs:
"""Represents the arguments to pass to the ``codeLens.evaluateSum`` command"""
uri: str
"""The uri of the document to edit"""
left: int
"""The left argument to ``+``"""
right: int
"""The right argument to ``+``"""
line: int
"""The line number to edit"""
@server.command("codeLens.evaluateSum")
def evaluate_sum(ls: LanguageServer, args: EvaluateSumArgs):
logging.info("arguments: %s", args) # here args is an instance of EvaluateSumArgs
document = ls.workspace.get_text_document(args.uri)
line = document.lines[args.line]
# Compute the edit that will update the document with the result.
answer = args.left + args.right
edit = types.TextDocumentEdit(
text_document=types.OptionalVersionedTextDocumentIdentifier(
uri=args.uri,
version=document.version,
),
edits=[
types.TextEdit(
new_text=f"{line.strip()} {answer}\n",
range=types.Range(
start=types.Position(line=args.line, character=0),
end=types.Position(line=args.line + 1, character=0),
),
)
],
)
# Apply the edit.
ls.workspace_apply_edit(
types.ApplyWorkspaceEditParams(
edit=types.WorkspaceEdit(document_changes=[edit]),
),
)
Renamed LanguageServer Methods¶
The LanuageServer class has been moved to the pygls.lsp module:
# Before
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
# After
from pygls.lsp.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
All LSP requests and notifications that can be sent by a server are now automatically generated from the specification, as a result the following methods have been renamed
pygls v1 |
pygls v2 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Additionally all LSP method signatures now require an instance of the corresponding params object for the method.
For example:
# Before
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
server.publish_diagnostics(uri='...', diagnostics=[...])
# After
from lsprotocol import types
from pygls.lsp.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
server.text_document_publish_diagnostics(
types.PublishDiagnosticsParams(
uri='...',
diagnostics=[...],
)
)
Renamed LanguageServer.progress¶
A consequence of the automatic method generation LanguageServer.progress now sends a $/progress notification, rather than giving access to pygls’ Progress helper.
The helper is now accessed via LanguageServer.work_done_progress
Before
from lsprotocol import types
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
@server.command('progress.example')
async def progress(ls: LanguageServer, *args):
"""Create and start the progress on the client."""
token = str(uuid.uuid4())
# Create
await ls.progress.create_async(token)
# Begin
ls.progress.begin(
token,
types.WorkDoneProgressBegin(title="Indexing", percentage=0, cancellable=True),
)
# Report
for i in range(1, 10):
# Check for cancellation from client
if ls.progress.tokens[token].cancelled():
# ... and stop the computation if client cancelled
return
ls.progress.report(
token,
types.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10),
)
await asyncio.sleep(2)
# End
ls.progress.end(token, types.WorkDoneProgressEnd(message="Finished"))
After
from lsprotocol import types
from pygls.lsp.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
@server.command('progress.example')
async def progress(ls: LanguageServer, *args):
"""Create and start the progress on the client."""
token = str(uuid.uuid4())
# Create
await ls.work_done_progress.create_async(token)
# Begin
ls.work_done_progress.begin(
token,
types.WorkDoneProgressBegin(title="Indexing", percentage=0, cancellable=True),
)
# Report
for i in range(1, 10):
# Check for cancellation from client
if ls.work_done_progress.tokens[token].cancelled():
# ... and stop the computation if client cancelled
return
ls.work_done_progress.report(
token,
types.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10),
)
await asyncio.sleep(2)
# End
ls.work_done_progress.end(token, types.WorkDoneProgressEnd(message="Finished"))
Renamed LSP Types¶
As part of the update to lsprotocol v2025, the following types have been renamed.
lsprotocol 2023.x |
lsprotocol 2025.x |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Low Level Changes¶
The following changes are unlikely to affect you directly, but have been included for completeness.
LanguageServer.lsp is now LanguageServer.protocol¶
If you need to access the underlying protocol object this is now via the protocol attribute.
pygls.server.Server is now pygls.server.JsonRPCServer¶
pygls’ base server class has been renamed
Removed loop argument from pygls.server.JsonRPCServer¶
Servers and clients in pygls v2 now both use the high level asyncio API, removing the need for an explicit loop argument to be passed in.
If you need control over the event loop used by pygls you can use functions like asyncio.set_event_loop() before starting the server/client.
Removed pygls.protocol.lsp_meta module¶
The implementation of pygls’ built-in handlers has changed in v2 and no longer relies on the LSPMeta metaclass and associated call_user_feature function.
Therefore both items and the containing module has been removed.
Removed multiprocessing.pool.ThreadPool¶
The multiprocessing.pool.ThreadPool instance has been removed, pygls now makes use of concurrent.futures.ThreadPoolExecutor for all threaded tasks.
The thread_pool_executor attribute of the base JsonRPCServer class has been removed, the ThreadPoolExecutor can be accessed via the thread_pool attribute instead.
JsonRPCProtocol is no longer an asyncio.Protocol¶
Now the pygls v2 uses the high-level asyncio APIs, it no longer makes sense for the JsonRPCProtocol class to inherit from asyncio.Protocol.
Similarly, “output” classes are now called writers rather than transports. The connection_made method has been replaced with a corresponding set_writer method.
New pygls.io_ module¶
There is a new pygls.io_ module containing main message parsing loop code common to both client and server
The equivlaent to pygls v1’s
pygls.server.aio_readlinefunction is nowpygls.io_.run_asyncIt now contains classes like v1’s
WebsocketTransportAdapter, which have been renamed toWebSocketWriter