Skip to content

Python type converter bug #430

Open

Description

When I try to use lsprotocol.converters.get_converter to parse the following (only the key part included):

{
    "jsonrpc": "2.0",
    "id": "1195cd79-60e6-412b-bc6a-9c77b6e5d7c1",
    "result": {
        "capabilities": {
            "notebookDocumentSync": {
                "notebookSelector": [
                    {
                        "cells": [
                            {
                                "language": "python"
                            }
                        ]
                    }
                ],
                "save": false
            }
        }
    }
}

I'm getting the following error (again, only the key part included):

cattrs.errors.StructureHandlerNotFoundError: Unsupported type: typing.Union[str, lsprotocol.types.NotebookDocumentFilterNotebookType, lsprotocol.types.NotebookDocumentFilterScheme, lsprotocol.types.NotebookDocumentFilterPattern, NoneType]. Register a structure hook for it.

I'm not sure, but thing that looks suspicious to me there is NoneType.

Activity

  1. added
    bugIssue identified by VS Code Team member as probable bug
    and removed on Jul 22, 2025
  2. debonte commented on Jul 22, 2025

    @debonte
    Contributor

    Not sure what's wrong yet, but I was able to repro the issue with the code below.

    The issue seems to have something to do with handling NotebookDocumentFilterWithNotebook vs NotebookDocumentFilterWithCells. The input has cells, but the error is about parsing notebook (the NotebookDocumentFilter type).

    import json
    import lsprotocol.converters
    from lsprotocol.types import InitializeResult
    
    
    input = """{
        "capabilities": {
            "notebookDocumentSync": {
                "notebookSelector": [
                    {
                        "cells": [
                            {
                                "language": "python"
                            }
                        ]
                    }
                ],
                "save": false
            }
        }
    }"""
    
    converter = lsprotocol.converters.get_converter()
    
    obj = json.loads(input)
    
    try:
        converter.structure(obj, InitializeResult)
    except Exception as e:
        while getattr(e, 'exceptions', None) != None:
            e = e.exceptions[0]
        print(f"Error: {e}")
  3. mjpieters commented on Nov 26, 2025

    @mjpieters

    The reproducer can be simplified to:

    import lsprotocol.converters
    import lsprotocol.types
    converter = lsprotocol.converters.get_converter()
    object_ = {"cells": [{"language": "python"}]}
    converter.structure(object_, lsprotocol.types.NotebookDocumentFilterWithCells)
    

    The issue is with the notebook field:

        noteook: Optional[Union[str, NotebookDocumentFilter]] = attrs.field(default=Nbone)
        """The notebook to be synced If a string
        value is provided it matches against the
        notebook type. '*' matches every notebook."""

    for which there is no hook defined, because it is optional. There is only a hook for Union[str, NotebookDocumentFilter] which doesn't account for None.

    The work-around is to register such a hook:

    from typing import Any
    
    @converter.register_structure_hook
    def _(
        object_: Any, _: object
    ) -> (
        str
        | lsprotocol.types.NotebookDocumentFilterNotebookType
        | lsprotocol.types.NotebookDocumentFilterScheme
        | lsprotocol.types.NotebookDocumentFilterPattern
        | None
    ):
        if object_ is None:
            return None
        elif isinstance(object_, str):
            return str(object_)
        elif "notebookType" in object_:
            return converter.structure(
                object_, lsprotocol.types.NotebookDocumentFilterNotebookType
            )
        elif "scheme" in object_:
            return converter.structure(object_, lsprotocol.types.NotebookDocumentFilterScheme)
        else:
            return converter.structure(object_, lsprotocol.types.NotebookDocumentFilterPattern)
  4. Shubham-Padkonde commented on Sep 20, 2026

    @Shubham-Padkonde

    This is the same root cause as #462, and #466 fixes it.

    NotebookDocumentFilterWithCells.notebook is typed Optional[Union[str, NotebookDocumentFilterNotebookType, NotebookDocumentFilterScheme, NotebookDocumentFilterPattern]], but _hooks.py registers _notebook_filter_hook only for the non-optional union, so the optional form has no structure hook — which is the NoneType you spotted in the message.

    Checked against your payload on current main (cattrs 26.2.0):

    # main
    FAIL ClassValidationError While structuring InitializeResult (1 sub-exception)
      cattrs.errors.StructureHandlerNotFoundError: Unsupported type:
      str | NotebookDocumentFilterNotebookType | NotebookDocumentFilterScheme | NotebookDocumentFilterPattern | None
    
    # main + #466
    OK NotebookDocumentSyncOptions(notebook_selector=(NotebookDocumentFilterWithCells(
         cells=(NotebookCellLanguage(language='python'),), notebook=None),), save=False)
    

    So nothing extra is needed here — this issue should close along with #466.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssue identified by VS Code Team member as probable bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions