11from __future__ import annotations
22
3- import pathlib
3+ import os . path
44import subprocess
55import sys
6- from typing import Callable
6+ import tempfile
77
8- import pytest
98
9+ def call_mypy (src : str , * , plugins : list [str ] | None = None ) -> tuple [int , str ]:
10+ if plugins is None :
11+ plugins = ["tools.mypy_helpers.plugin" ]
12+ with tempfile .TemporaryDirectory () as tmpdir :
13+ cfg = os .path .join (tmpdir , "mypy.toml" )
14+ with open (cfg , "w" ) as f :
15+ f .write (f"[tool.mypy]\n plugins = { plugins !r} \n " )
1016
11- @pytest .fixture
12- def call_mypy (tmp_path : pathlib .Path ) -> Callable [[str ], tuple [int , str ]]:
13- cfg = """\
14- [tool.mypy]
15- plugins = ["tools.mypy_helpers.plugin"]
16- """
17- cfg_path = tmp_path .joinpath ("mypy.toml" )
18- cfg_path .write_text (cfg )
19-
20- def _call_mypy (contents : str ) -> tuple [int , str ]:
2117 ret = subprocess .run (
2218 (
2319 * (sys .executable , "-m" , "mypy" ),
24- * ("--config" , str ( cfg_path ) ),
25- * ("-c" , contents ),
20+ * ("--config" , cfg ),
21+ * ("-c" , src ),
2622 ),
2723 capture_output = True ,
2824 encoding = "UTF-8" ,
2925 )
3026 return ret .returncode , ret .stdout
3127
32- return _call_mypy
33-
3428
35- def test_invalid_get_connection_call (call_mypy ):
29+ def test_invalid_get_connection_call ():
3630 code = """
3731from django.db.transaction import get_connection
3832
@@ -48,7 +42,7 @@ def test_invalid_get_connection_call(call_mypy):
4842 assert out == expected
4943
5044
51- def test_ok_get_connection (call_mypy ):
45+ def test_ok_get_connection ():
5246 code = """
5347from django.db.transaction import get_connection
5448
@@ -59,7 +53,7 @@ def test_ok_get_connection(call_mypy):
5953 assert ret == 0
6054
6155
62- def test_invalid_transaction_atomic (call_mypy ):
56+ def test_invalid_transaction_atomic ():
6357 code = """
6458from django.db import transaction
6559
@@ -78,7 +72,7 @@ def test_invalid_transaction_atomic(call_mypy):
7872 assert out == expected
7973
8074
81- def test_ok_transaction_atomic (call_mypy ):
75+ def test_ok_transaction_atomic ():
8276 code = """
8377from django.db import transaction
8478
@@ -89,7 +83,7 @@ def test_ok_transaction_atomic(call_mypy):
8983 assert ret == 0
9084
9185
92- def test_ok_transaction_on_commit (call_mypy ):
86+ def test_ok_transaction_on_commit ():
9387 code = """
9488from django.db import transaction
9589
@@ -102,7 +96,7 @@ def completed():
10296 assert ret == 0
10397
10498
105- def test_invalid_transaction_on_commit (call_mypy ):
99+ def test_invalid_transaction_on_commit ():
106100 code = """
107101from django.db import transaction
108102
@@ -120,7 +114,7 @@ def completed():
120114 assert out == expected
121115
122116
123- def test_invalid_transaction_set_rollback (call_mypy ):
117+ def test_invalid_transaction_set_rollback ():
124118 code = """
125119from django.db import transaction
126120
@@ -135,11 +129,55 @@ def test_invalid_transaction_set_rollback(call_mypy):
135129 assert out == expected
136130
137131
138- def test_ok_transaction_set_rollback (call_mypy ):
132+ def test_ok_transaction_set_rollback ():
139133 code = """
140134from django.db import transaction
141135
142136transaction.set_rollback(True, "default")
143137"""
144138 ret , _ = call_mypy (code )
145139 assert ret == 0
140+
141+
142+ def test_field_descriptor_hack ():
143+ code = """\
144+ from __future__ import annotations
145+
146+ from django.db import models
147+
148+ class M1(models.Model):
149+ f: models.Field[int, int] = models.IntegerField()
150+
151+ class C:
152+ f: int
153+
154+ def f(inst: C | M1 | M2) -> int:
155+ return inst.f
156+
157+ # should also work with field subclasses
158+ class F(models.Field[int, int]):
159+ pass
160+
161+ class M2(models.Model):
162+ f = F()
163+
164+ def g(inst: C | M2) -> int:
165+ return inst.f
166+ """
167+
168+ # should be an error with default plugins
169+ # mypy may fix this at some point hopefully: python/mypy#5570
170+ ret , out = call_mypy (code , plugins = [])
171+ assert ret
172+ assert (
173+ out
174+ == """\
175+ <string>:12: error: Incompatible return value type (got "Union[int, Field[int, int]]", expected "int") [return-value]
176+ <string>:22: error: Incompatible return value type (got "Union[int, F]", expected "int") [return-value]
177+ Found 2 errors in 1 file (checked 1 source file)
178+ """
179+ )
180+
181+ # should be fixed with our special plugin
182+ ret , _ = call_mypy (code )
183+ assert ret == 0
0 commit comments