File: test_pyright.py

package info (click to toggle)
python-attrs 25.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,004 kB
  • sloc: python: 10,495; makefile: 153
file content (108 lines) | stat: -rw-r--r-- 2,798 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# SPDX-License-Identifier: MIT

from __future__ import annotations

import json
import shutil
import subprocess

from pathlib import Path

import pytest


pytestmark = [
    pytest.mark.skipif(
        shutil.which("pyright") is None, reason="Requires pyright."
    ),
]


def parse_pyright_output(test_file: Path) -> set[tuple[str, str]]:
    pyright = subprocess.run(  # noqa: PLW1510
        ["pyright", "--outputjson", str(test_file)], capture_output=True
    )

    pyright_result = json.loads(pyright.stdout)

    # We use tuples instead of proper classes to get nicer diffs from pytest.
    return {
        (d["severity"], d["message"])
        for d in pyright_result["generalDiagnostics"]
    }


def test_pyright_baseline():
    """
    The typing.dataclass_transform decorator allows pyright to determine
    attrs decorated class types.
    """

    test_file = Path(__file__).parent / "dataclass_transform_example.py"

    diagnostics = parse_pyright_output(test_file)

    expected_diagnostics = {
        (
            "information",
            'Type of "Define.__init__" is "(self: Define, a: str, b: int) -> None"',
        ),
        (
            "information",
            'Type of "DefineConverter.__init__" is '
            '"(self: DefineConverter, with_converter: str | Buffer | '
            'SupportsInt | SupportsIndex | SupportsTrunc) -> None"',
        ),
        (
            "error",
            'Cannot assign to attribute "a" for class '
            '"Frozen"\n\xa0\xa0Attribute "a" is read-only',
        ),
        (
            "information",
            'Type of "d.a" is "Literal[\'new\']"',
        ),
        (
            "error",
            'Cannot assign to attribute "a" for class '
            '"FrozenDefine"\n\xa0\xa0Attribute "a" is read-only',
        ),
        (
            "information",
            'Type of "d2.a" is "Literal[\'new\']"',
        ),
        (
            "information",
            'Type of "af.__init__" is "(_a: int) -> None"',
        ),
    }

    assert expected_diagnostics == diagnostics


def test_pyright_attrsinstance_compat(tmp_path):
    """
    Test that `AttrsInstance` is compatible with Pyright.
    """
    test_pyright_attrsinstance_compat_path = (
        tmp_path / "test_pyright_attrsinstance_compat.py"
    )
    test_pyright_attrsinstance_compat_path.write_text(
        """\
import attrs

# We can assign any old object to `AttrsInstance`.
foo: attrs.AttrsInstance = object()

reveal_type(attrs.AttrsInstance)
"""
    )

    diagnostics = parse_pyright_output(test_pyright_attrsinstance_compat_path)
    expected_diagnostics = {
        (
            "information",
            'Type of "attrs.AttrsInstance" is "type[AttrsInstance]"',
        )
    }
    assert diagnostics == expected_diagnostics