Skip to content

Commit dd87226

Browse files
authored
Merge pull request #26 from George-Ogden/broken-cache
Continue working when cache valued result is missing
2 parents 3876d3b + d181bdf commit dd87226

File tree

7 files changed

+121
-21
lines changed

7 files changed

+121
-21
lines changed

mypy_pytest_plugin/fixture.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,39 @@ class Fixture:
3838
arguments: Sequence[TestArgument]
3939
scope: FixtureScope
4040
type_variables: Sequence[TypeVarLikeType]
41-
context: FuncDef
41+
context: Context
4242

4343
@classmethod
4444
def from_decorator(cls, decorator: Decorator, checker: TypeChecker) -> Fixture | None:
4545
return FixtureParser(checker).from_decorator(decorator)
4646

4747
@classmethod
48-
def from_type(cls, type: CallableType, *, scope: FixtureScope, file: str) -> Self:
48+
def from_type(
49+
cls,
50+
type: CallableType,
51+
*,
52+
scope: FixtureScope,
53+
file: str,
54+
is_generator: bool,
55+
fullname: str,
56+
) -> Self:
4957
func = type.definition
50-
assert isinstance(func, FuncDef)
51-
assert isinstance(func.type, CallableType)
52-
arguments = TestArgument.from_fn_def(func, checker=None, source="fixture")
53-
assert arguments is not None
58+
assert isinstance(func, FuncDef | None)
59+
if isinstance(func, FuncDef):
60+
arguments = TestArgument.from_fn_def(func, checker=None, source="fixture")
61+
assert arguments is not None
62+
context: Context = func
63+
else:
64+
arguments = TestArgument.from_type(type)
65+
context = Context()
5466
return cls(
55-
fullname=Fullname.from_string(func.fullname),
67+
fullname=Fullname.from_string(fullname),
5668
file=file,
57-
return_type=FixtureParser.fixture_return_type(
58-
func.type.ret_type, is_generator=func.is_generator
59-
),
69+
return_type=FixtureParser.fixture_return_type(type.ret_type, is_generator=is_generator),
6070
arguments=arguments,
6171
scope=scope,
62-
context=func,
63-
type_variables=func.type.variables,
72+
context=context,
73+
type_variables=type.variables,
6474
)
6575

6676
@classmethod

mypy_pytest_plugin/fixture_manager.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from _pytest.fixtures import FixtureManager as PytestFixtureManager
99
from _pytest.main import Session
1010
from mypy.checker import TypeChecker
11-
from mypy.nodes import FuncDef, MypyFile
11+
from mypy.nodes import MypyFile
1212
from mypy.types import CallableType, Instance, LiteralType
1313
from pytest import FixtureDef # noqa: PT013
1414

@@ -121,12 +121,17 @@ def _module_lookup(self, module: MypyFile, request_name: str) -> Fixture | None:
121121
and isinstance(type_ := decorator.type, Instance)
122122
and type_.type.fullname == f"{TYPES_MODULE}.fixture_type.FixtureType"
123123
):
124-
[scope, signature] = type_.args
124+
[scope, signature, is_generator, fullname] = type_.args
125125
assert isinstance(scope, LiteralType)
126126
assert isinstance(signature, CallableType)
127-
assert isinstance(signature.definition, FuncDef)
127+
assert isinstance(is_generator, LiteralType)
128+
assert isinstance(fullname, LiteralType)
128129
return Fixture.from_type(
129-
signature, scope=cast(FixtureScope, scope.value), file=module.path
130+
signature,
131+
scope=cast(FixtureScope, scope.value),
132+
file=module.path,
133+
is_generator=cast(bool, is_generator.value),
134+
fullname=cast(str, fullname.value),
130135
)
131136
return None
132137

mypy_pytest_plugin/fixture_test.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from mypy.nodes import Decorator
22
from mypy.subtypes import is_same_type
3+
from mypy.types import CallableType
34
import pytest
45

5-
from .fixture import Fixture, FixtureParser
6+
from .fixture import Fixture, FixtureParser, FixtureScope
67
from .test_utils import check_error_messages, get_error_messages, parse
78

89

@@ -247,6 +248,55 @@ def fixture(x, y):
247248
)
248249

249250

251+
def _fixture_from_type_test_body(defs: str) -> None:
252+
parse_result = parse(defs)
253+
fixture_node = parse_result.defs["fixture"]
254+
assert isinstance(fixture_node, Decorator)
255+
256+
checker = parse_result.checker
257+
parse_result.accept_all()
258+
259+
fixture_type = fixture_node.func.type
260+
assert isinstance(fixture_type, CallableType)
261+
fixture_type.definition = None
262+
263+
fixture = Fixture.from_type(
264+
fixture_type,
265+
scope=FixtureScope.module,
266+
file="",
267+
is_generator=False,
268+
fullname="test_module.fullname",
269+
)
270+
assert fixture is not None
271+
272+
messages = get_error_messages(checker)
273+
assert not checker.errors.is_errors(), messages
274+
275+
276+
def test_fixture_from_types_no_args() -> None:
277+
_fixture_from_type_test_body(
278+
"""
279+
import pytest
280+
281+
@pytest.fixture
282+
def fixture() -> None:
283+
...
284+
"""
285+
)
286+
287+
288+
def test_fixture_from_type_some_args() -> None:
289+
_fixture_from_type_test_body(
290+
"""
291+
import pytest
292+
293+
@pytest.fixture
294+
def fixture(x: int, y: bool) -> None:
295+
...
296+
"""
297+
)
298+
299+
250300
def fixture_return_type_test_body(defs: str, is_generator: bool) -> None:
251301
parse_result = parse(defs)
252302
original_type = parse_result.types["original"]

mypy_pytest_plugin/plugin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ def _fixture_type(cls, fixture: Fixture, *, decorator: Decorator, checker: TypeC
150150
[
151151
LiteralType(fixture.scope, fallback=checker.named_type("builtins.object")),
152152
decorator.func.type,
153+
LiteralType(
154+
decorator.func.is_generator, fallback=checker.named_type("builtins.object")
155+
),
156+
LiteralType(decorator.fullname, fallback=checker.named_type("builtins.object")),
153157
],
154158
)
155159

mypy_pytest_plugin/test_argument.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from collections.abc import Sequence
44
from dataclasses import dataclass
5-
from typing import Literal
5+
from typing import Literal, Self
66

77
from mypy.checker import TypeChecker
88
from mypy.errorcodes import ErrorCode
@@ -15,7 +15,15 @@
1515
TypeInfo,
1616
)
1717
from mypy.subtypes import is_subtype
18-
from mypy.types import AnyType, CallableType, Instance, Type, TypeOfAny, TypeVarLikeType
18+
from mypy.types import (
19+
AnyType,
20+
CallableType,
21+
FormalArgument,
22+
Instance,
23+
Type,
24+
TypeOfAny,
25+
TypeVarLikeType,
26+
)
1927

2028
from .error_codes import (
2129
OPTIONAL_ARGUMENT,
@@ -39,6 +47,29 @@ def from_fn_def(
3947
) -> Sequence[TestArgument] | None:
4048
return TestArgumentParser(checker).parse_fn_def(fn_def, source=source)
4149

50+
@classmethod
51+
def from_type(cls, type: CallableType) -> Sequence[TestArgument]:
52+
return [
53+
argument
54+
for formal_argument in type.formal_arguments()
55+
if (
56+
argument := TestArgument.from_formal_argument(
57+
formal_argument, type_variables=type.variables
58+
)
59+
)
60+
is not None
61+
]
62+
63+
@classmethod
64+
def from_formal_argument(
65+
cls, argument: FormalArgument, type_variables: Sequence[TypeVarLikeType]
66+
) -> Self | None:
67+
if argument.name is None or argument.name == "request":
68+
return None
69+
return cls(
70+
name=argument.name, type_=argument.typ, type_variables=type_variables, context=Context()
71+
)
72+
4273

4374
@dataclass(frozen=True, slots=True)
4475
class TestArgumentParser:

mypy_pytest_plugin_types/fixture_type.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ from collections.abc import Callable
22

33
from _pytest.fixtures import FixtureFunctionDefinition
44

5-
class FixtureType[S, F: Callable](FixtureFunctionDefinition): ...
5+
class FixtureType[S: str, F: Callable, G: bool, N: str](FixtureFunctionDefinition): ...

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "mypy-pytest-plugin"
33
requires-python = ">=3.12,<3.15"
4-
version = "0.7.0"
4+
version = "0.7.1"
55
dynamic = ["dependencies"]
66

77
[tool.setuptools]

0 commit comments

Comments
 (0)