File: test_expressions.py

package info (click to toggle)
python-griffe 1.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,256 kB
  • sloc: python: 16,348; javascript: 84; makefile: 47; sh: 24
file content (165 lines) | stat: -rw-r--r-- 5,534 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""Test names and expressions methods."""

from __future__ import annotations

import ast
import sys

import pytest

from griffe import Module, Parser, get_expression, temporary_visited_module
from tests.test_nodes import syntax_examples


@pytest.mark.parametrize(
    ("annotation", "items"),
    [
        ("tuple[int, float] | None", 2),
        ("None | tuple[int, float]", 2),
        ("Optional[tuple[int, float]]", 2),
        ("typing.Optional[tuple[int, float]]", 2),
    ],
)
def test_explode_return_annotations(annotation: str, items: int) -> None:
    """Check that we correctly split items from return annotations.

    Parameters:
        annotation: The return annotation.
        items: The number of items to write in the docstring returns section.
    """
    newline = "\n            "
    returns = newline.join(f"x{_}: Some value." for _ in range(items))
    code = f"""
    import typing
    from typing import Optional

    def function() -> {annotation}:
        '''This function returns either two ints or None

        Returns:
            {returns}
        '''
    """
    with temporary_visited_module(code) as module:
        sections = module["function"].docstring.parse(Parser.google)
        assert sections[1].value


@pytest.mark.parametrize(
    "annotation",
    [
        "int",
        "tuple[int]",
        "dict[str, str]",
        "Optional[tuple[int, float]]",
    ],
)
def test_full_expressions(annotation: str) -> None:
    """Assert we can transform expressions to their full form without errors."""
    code = f"x: {annotation}"
    with temporary_visited_module(code) as module:
        assert str(module["x"].annotation) == annotation


def test_resolving_full_names() -> None:
    """Assert expressions are correctly transformed to their fully-resolved form."""
    with temporary_visited_module(
        """
        from package import module
        attribute1: module.Class

        from package import module as mod
        attribute2: mod.Class
        """,
    ) as module:
        assert module["attribute1"].annotation.canonical_path == "package.module.Class"
        assert module["attribute2"].annotation.canonical_path == "package.module.Class"


@pytest.mark.parametrize("code", syntax_examples)
def test_expressions(code: str) -> None:
    """Test building annotations from AST nodes.

    Parameters:
        code: An expression (parametrized).
    """
    top_node = compile(code, filename="<>", mode="exec", flags=ast.PyCF_ONLY_AST, optimize=2)
    expression = get_expression(top_node.body[0].value, parent=Module("module"))  # type: ignore[attr-defined]
    assert str(expression) == code


def test_length_one_tuple_as_string() -> None:
    """Length-1 tuples must have a trailing comma."""
    code = "x = ('a',)"
    with temporary_visited_module(code) as module:
        assert str(module["x"].value) == "('a',)"


def test_resolving_init_parameter() -> None:
    """Instance attribute values should resolve to matching parameters.

    They must not resolve to the member of the same name in the same class,
    or to objects with the same name in higher scopes.
    """
    with temporary_visited_module(
        """
        x = 1

        class Class:
            def __init__(self, x: int):
                self.x: int = x
        """,
    ) as module:
        assert module["Class.x"].value.canonical_path == "module.Class(x)"


@pytest.mark.parametrize(
    "code",
    [
        # Core.
        "a * (b + c)",  # Lower precedence as a sub-expression of one that has higher precedence.
        "(a and b) == c",
        "((a | b) + c).d",
        "a - (b - c)",  # Left-association.
        "(a ** b) ** c",  # Right-association.
        # Unary operator and edge cases:
        # > The power operator `**` binds less tightly than an arithmetic
        # > or bitwise unary operator on its right, that is, `2**-1` is `0.5`.
        "a ** -b",
        "-a ** b",
        "(-a) ** b",
        # Misc: conditionals, lambdas, comprehensions and generators.
        "(lambda: 0).a",
        "(lambda x: a + x if b else c)(d).e",
        "a if (b if c else d) else e",  # Right-association.
        "(a if b else c) if d else e",  # Forced left-association.
        "(a for a in b).c",
    ],
)
def test_parentheses_preserved(code: str) -> None:
    """Parentheses used to enforce an order of operations should not be removed."""
    with temporary_visited_module(f"val = {code}") as module:
        value_expr = module["val"].value
        assert str(value_expr) == code


# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 generics")
def test_resolving_type_parameters() -> None:
    """Assert type parameters are correctly transformed to their fully-resolved form."""
    with temporary_visited_module(
        """
        class C[T]:
            class D[T]:
                def func[Y](self, arg1: T, arg2: Y): pass
                attr: T
            def func[Z](arg1: T, arg2: Y): pass
        """,
    ) as module:
        assert module["C.D.func"].parameters["arg1"].annotation.canonical_path == "module.C.D[T]"
        assert module["C.D.func"].parameters["arg2"].annotation.canonical_path == "module.C.D.func[Y]"

        assert module["C.D.attr"].annotation.canonical_path == "module.C.D[T]"

        assert module["C.func"].parameters["arg1"].annotation.canonical_path == "module.C[T]"
        assert module["C.func"].parameters["arg2"].annotation.canonical_path == "Y"