Migrating to v1.0

The most notable change of the v1.0 release of pygls is the removal of its hand written LSP type and method definitions in favour of relying on the types provided by the lsprotocol library which are automatically generated from the LSP specification. As as side effect this has also meant the removal of pydantic as a dependency, since lsprotocol uses attrs and cattrs for serialisation and validation.

This guide outlines how to adapt an existing server to the breaking changes introduced in this release.

Known Migrations

You may find insight and inspiration from these projects that have already successfully migrated to v1:

Updating Imports

The pygls.lsp.methods and pygls.lsp.types modules no longer exist. Instead, all types and method names should now be imported from the lsprotocol.types module.

Additionally, the following types and constants have been renamed.

pygls

lsprotocol

CODE_ACTION

TEXT_DOCUMENT_CODE_ACTION

CODE_LENS

TEXT_DOCUMENT_CODE_LENS

COLOR_PRESENTATION

TEXT_DOCUMENT_COLOR_PRESENTATION

COMPLETION

TEXT_DOCUMENT_COMPLETION

DECLARATION

TEXT_DOCUMENT_DECLARATION

DEFINITION

TEXT_DOCUMENT_DEFINITION

DOCUMENT_COLOR

TEXT_DOCUMENT_DOCUMENT_COLOR

DOCUMENT_HIGHLIGHT

TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT

DOCUMENT_LINK

TEXT_DOCUMENT_DOCUMENT_LINK

DOCUMENT_SYMBOL

TEXT_DOCUMENT_DOCUMENT_SYMBOL

FOLDING_RANGE

TEXT_DOCUMENT_FOLDING_RANGE

FORMATTING

TEXT_DOCUMENT_FORMATTING

HOVER

TEXT_DOCUMENT_HOVER

IMPLEMENTATION

TEXT_DOCUMENT_IMPLEMENTATION

LOG_TRACE_NOTIFICATION

LOG_TRACE

ON_TYPE_FORMATTING

TEXT_DOCUMENT_ON_TYPE_FORMATTING

PREPARE_RENAME

TEXT_DOCUMENT_PREPARE_RENAME

PROGRESS_NOTIFICATION

PROGRESS

RANGE_FORMATTING

TEXT_DOCUMENT_RANGE_FORMATTING

REFERENCES

TEXT_DOCUMENT_REFERENCES

RENAME

TEXT_DOCUMENT_RENAME

SELECTION_RANGE

TEXT_DOCUMENT_SELECTION_RANGE

SET_TRACE_NOTIFICATION

SET_TRACE

SIGNATURE_HELP

TEXT_DOCUMENT_SIGNATURE_HELP

TEXT_DOCUMENT_CALL_HIERARCHY_INCOMING_CALLS

CALL_HIERARCHY_INCOMING_CALLS

TEXT_DOCUMENT_CALL_HIERARCHY_OUTGOING_CALLS

CALL_HIERARCHY_OUTGOING_CALLS

TEXT_DOCUMENT_CALL_HIERARCHY_PREPARE

TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY

TYPE_DEFINITION

TEXT_DOCUMENT_TYPE_DEFINITION

WORKSPACE_FOLDERS

WORKSPACE_WORKSPACE_FOLDERS

ApplyWorkspaceEditResponse

ApplyWorkspaceEditResult

ClientInfo

InitializeParamsClientInfoType

CodeActionDisabled

CodeActionDisabledType

CodeActionLiteralSupportActionKindClientCapabilities

CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType

CodeActionLiteralSupportClientCapabilities

CodeActionClientCapabilitiesCodeActionLiteralSupportType

CompletionItemClientCapabilities

CompletionClientCapabilitiesCompletionItemType

CompletionItemKindClientCapabilities

CompletionClientCapabilitiesCompletionItemKindType

CompletionTagSupportClientCapabilities

CompletionClientCapabilitiesCompletionItemTypeTagSupportType

DocumentSymbolCapabilitiesTagSupport

DocumentSymbolClientCapabilitiesTagSupportType

InsertTextModeSupportClientCapabilities

CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType

MarkedStringType

MarkedString

MarkedString

MarkedString_Type1

PrepareRename

PrepareRenameResult_Type1

PublishDiagnosticsTagSupportClientCapabilities

PublishDiagnosticsClientCapabilitiesTagSupportType

ResolveSupportClientCapabilities

CodeActionClientCapabilitiesResolveSupportType

SemanticTokensRequestsFull

SemanticTokensRegistrationOptionsFullType1

SemanticTokensRequests

SemanticTokensClientCapabilitiesRequestsType

ServerInfo

InitializeResultServerInfoType

ShowMessageRequestActionItem

ShowMessageRequestClientCapabilitiesMessageActionItemType

SignatureHelpInformationClientCapabilities

SignatureHelpClientCapabilitiesSignatureInformationType

SignatureHelpInformationParameterInformationClientCapabilities

SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType

TextDocumentContentChangeEvent

TextDocumentContentChangeEvent_Type1

TextDocumentContentChangeTextEvent

TextDocumentContentChangeEvent_Type2

TextDocumentSyncOptionsServerCapabilities

TextDocumentSyncOptions

Trace

TraceValues

URI

str

WorkspaceCapabilitiesSymbolKind

WorkspaceSymbolClientCapabilitiesSymbolKindType

WorkspaceCapabilitiesTagSupport

WorkspaceSymbolClientCapabilitiesTagSupportType

WorkspaceFileOperationsServerCapabilities

FileOperationOptions

WorkspaceServerCapabilities

ServerCapabilitiesWorkspaceType

Custom Models

One of the most obvious changes is the switch to attrs and cattrs for serialization and deserialisation. This means that any custom models used by your language server will need to be converted to an attrs style class.

# Before
from pydantic import BaseModel, Field

class ExampleConfig(BaseModel):
    build_dir: Optional[str] = Field(None, alias="buildDir")

    builder_name: str = Field("html", alias="builderName")

    conf_dir: Optional[str] = Field(None, alias="confDir")
# After
import attrs

@attrs.define
class ExampleConfig:
    build_dir: Optional[str] = attrs.field(default=None)

    builder_name: str = attrs.field(default="html")

    conf_dir: Optional[str] = attrs.field(default=None)

Pygls provides a default converter that it will use when converting your models to/from JSON, which should be sufficient for most scenarios.

>>> from pygls.protocol import default_converter
>>> converter = default_converter()

>>> config = ExampleConfig(builder_name='epub', conf_dir='/path/to/conf')
>>> converter.unstructure(config)
{'builderName': 'epub', 'confDir': '/path/to/conf'}   # Note how snake_case is converted to camelCase

>>> converter.structure({'builderName': 'epub', 'confDir': '/path/to/conf'}, ExampleConfig)
ExampleConfig(build_dir=None, builder_name='epub', conf_dir='/path/to/conf')

However, depending on the complexity of your type definitions you may find the default converter fail to parse some of your types.

>>> from typing import Literal, Union

>>> @attrs.define
... class ExampleConfig:
...     num_jobs: Union[Literal["auto"], int] = attrs.field(default='auto')
...

>>> converter.structure({'numJobs': 'auto'}, ExampleConfig)
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 1, in <module>
  |   File "/.../python3.10/site-packages/cattrs/converters.py", li
ne 309, in structure
  |     return self._structure_func.dispatch(cl)(obj, cl)
  |   File "<cattrs generated structure __main__.ExampleConfig-2>", line 10, in structure_ExampleConfig
  |     if errors: raise __c_cve('While structuring ' + 'ExampleConfig', errors, __cl)
  | cattrs.errors.ClassValidationError: While structuring ExampleConfig (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<cattrs generated structure __main__.ExampleConfig-2>", line 6, in structure_ExampleConfig
    |     res['num_jobs'] = __c_structure_num_jobs(o['numJobs'], __c_type_num_jobs)
    |   File "/.../python3.10/site-packages/cattrs/converters.py",
line 377, in _structure_error
    |     raise StructureHandlerNotFoundError(msg, type_=cl)
    | cattrs.errors.StructureHandlerNotFoundError: Unsupported type: typing.Union[typing.Literal['auto'], int].
 Register a structure hook for it.
    | Structuring class ExampleConfig @ attribute num_jobs
    +------------------------------------

In which case you can extend the converter provided by pygls with your own structure hooks

from pygls.protocol import default_converter

def custom_converter():
    converter = default_converter()
    converter.register_structure_hook(Union[Literal['auto', int], lambda obj, _: obj)

    return converter

You can then override the default converter used by pygls when constructing your language server instance

server = LanguageServer(
    name="my-language-server", version="v1.0", converter_factory=custom_converter
)

See the hooks.py module in lsprotocol for some example structure hooks

Miscellaneous

Mandatory name and version

It is now necessary to provide a name and version when constructing an instance of the LanguageServer class

from pygls.server import LanguageServer

server = LanguageServer(name="my-language-server", version="v1.0")

ClientCapabilities.get_capability is now get_capability

# Before
from pygls.lsp.types import ClientCapabilities

client_capabilities = ClientCapabilities()
commit_character_support = client_capabilities.get_capability(
   "text_document.completion.completion_item.commit_characters_support", False
)
# After
from lsprotocol.types import ClientCapabilities
from pygls.capabilities import get_capability

client_capabilities = ClientCapabilities()
commit_character_support = get_capability(
   client_capabilities,
   "text_document.completion.completion_item.commit_characters_support",
   False
)