Skip to content

Commit ea93332

Browse files
authored
Update mypy pin and fix related issues (#127)
1 parent 6d07636 commit ea93332

File tree

7 files changed

+80
-25
lines changed

7 files changed

+80
-25
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
mkdir tmpd
3131
cp -r sqlalchemy-stubs tmpd/sqlalchemy
3232
cd tmpd
33-
python3 -m mypy --new-semantic-analyzer -p sqlalchemy
33+
python3 -m mypy -p sqlalchemy
3434
- name: "run flake8 on stubs"
3535
python: 3.7
3636
script: |

external/mypy

Submodule mypy updated 446 files

sqlalchemy-stubs/engine/result.pyi

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import sys
2+
13
from typing import Any, List, Mapping, Optional, Iterator, Union, AbstractSet, Tuple
24
from ..sql.schema import Column
35

6+
if sys.version_info >= (3, 0):
7+
_RowItems = AbstractSet[Tuple[str, Any]]
8+
else:
9+
_RowItems = List[Tuple[str, Any]]
10+
411
def rowproxy_reconstructor(cls, state): ...
512

613
class BaseRowProxy(Mapping[str, Any]):
@@ -22,7 +29,7 @@ class RowProxy(BaseRowProxy):
2229
def __eq__(self, other): ...
2330
def __ne__(self, other): ...
2431
def has_key(self, key): ...
25-
def items(self) -> AbstractSet[Tuple[str, Any]]: ...
32+
def items(self) -> _RowItems: ...
2633
def keys(self): ...
2734
def iterkeys(self): ...
2835
def itervalues(self): ...

sqlmypy.py

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
from mypy.plugins.common import add_method
77
from mypy.nodes import (
88
NameExpr, Expression, StrExpr, TypeInfo, ClassDef, Block, SymbolTable, SymbolTableNode, GDEF,
9-
Argument, Var, ARG_STAR2, MDEF, TupleExpr, RefExpr
9+
Argument, Var, ARG_STAR2, MDEF, TupleExpr, RefExpr, FuncBase, SymbolNode
1010
)
1111
from mypy.types import (
1212
UnionType, NoneTyp, Instance, Type, AnyType, TypeOfAny, UninhabitedType, CallableType
1313
)
1414
from mypy.typevars import fill_typevars_with_any
1515

16-
from typing import Optional, Callable, Dict, List, TypeVar
16+
try:
17+
from mypy.types import get_proper_type
18+
except ImportError:
19+
get_proper_type = lambda x: x
20+
21+
from typing import Optional, Callable, Dict, List, TypeVar, Union
1722

1823
MYPY = False # we should support Python 3.5.1 and cases where typing_extensions is not available.
1924
if MYPY:
@@ -29,6 +34,24 @@
2934
RELATIONSHIP_NAME = 'sqlalchemy.orm.relationships.RelationshipProperty' # type: Final
3035

3136

37+
# See https://github.com/python/mypy/issues/6617 for plugin API updates.
38+
39+
def fullname(x: Union[FuncBase, SymbolNode]) -> str:
40+
"""Compatibility helper for mypy 0.750 vs older."""
41+
fn = x.fullname
42+
if callable(fn):
43+
return fn()
44+
return fn
45+
46+
47+
def shortname(x: Union[FuncBase, SymbolNode]) -> str:
48+
"""Compatibility helper for mypy 0.750 vs older."""
49+
fn = x.name
50+
if callable(fn):
51+
return fn()
52+
return fn
53+
54+
3255
def is_declarative(info: TypeInfo) -> bool:
3356
"""Check if this is a subclass of a declarative base."""
3457
if info.mro:
@@ -92,7 +115,7 @@ def add_var_to_class(name: str, typ: Type, info: TypeInfo) -> None:
92115
"""
93116
var = Var(name)
94117
var.info = info
95-
var._fullname = info.fullname() + '.' + name
118+
var._fullname = fullname(info) + '.' + name
96119
var.type = typ
97120
info.names[name] = SymbolTableNode(MDEF, var)
98121

@@ -200,7 +223,7 @@ def model_hook(ctx: FunctionContext) -> Type:
200223
Note: this is still not perfect, since the context for inference of
201224
argument types is 'Any'.
202225
"""
203-
assert isinstance(ctx.default_return_type, Instance)
226+
assert isinstance(ctx.default_return_type, Instance) # type: ignore[misc]
204227
model = ctx.default_return_type.type
205228
metadata = model.metadata.get('sqlalchemy')
206229
if not metadata or not metadata.get('generated_init'):
@@ -211,11 +234,12 @@ def model_hook(ctx: FunctionContext) -> Type:
211234
expected_types = {} # type: Dict[str, Type]
212235
for cls in model.mro[::-1]:
213236
for name, sym in cls.names.items():
214-
if isinstance(sym.node, Var) and isinstance(sym.node.type, Instance):
215-
tp = sym.node.type
216-
if tp.type.fullname() in (COLUMN_NAME, RELATIONSHIP_NAME):
217-
assert len(tp.args) == 1
218-
expected_types[name] = tp.args[0]
237+
if isinstance(sym.node, Var):
238+
tp = get_proper_type(sym.node.type)
239+
if isinstance(tp, Instance):
240+
if fullname(tp.type) in (COLUMN_NAME, RELATIONSHIP_NAME):
241+
assert len(tp.args) == 1
242+
expected_types[name] = tp.args[0]
219243

220244
assert len(ctx.arg_names) == 1 # only **kwargs in generated __init__
221245
assert len(ctx.arg_types) == 1
@@ -226,14 +250,14 @@ def model_hook(ctx: FunctionContext) -> Type:
226250
continue
227251
if actual_name not in expected_types:
228252
ctx.api.fail('Unexpected column "{}" for model "{}"'.format(actual_name,
229-
model.name()),
253+
shortname(model)),
230254
ctx.context)
231255
continue
232256
# Using private API to simplify life.
233257
ctx.api.check_subtype(actual_type, expected_types[actual_name], # type: ignore
234258
ctx.context,
235259
'Incompatible type for "{}" of "{}"'.format(actual_name,
236-
model.name()),
260+
shortname(model)),
237261
'got', 'expected')
238262
return ctx.default_return_type
239263

@@ -277,7 +301,7 @@ def column_hook(ctx: FunctionContext) -> Type:
277301
278302
TODO: check the type of 'default'.
279303
"""
280-
assert isinstance(ctx.default_return_type, Instance)
304+
assert isinstance(ctx.default_return_type, Instance) # type: ignore[misc]
281305

282306
nullable_arg = get_argument_by_name(ctx, 'nullable')
283307
primary_arg = get_argument_by_name(ctx, 'primary_key')
@@ -309,13 +333,13 @@ def grouping_hook(ctx: FunctionContext) -> Type:
309333
Grouping(Column(String), nullable=False) -> Grouping[str]
310334
Grouping(Column(String)) -> Grouping[Optional[str]]
311335
"""
312-
assert isinstance(ctx.default_return_type, Instance)
336+
assert isinstance(ctx.default_return_type, Instance) # type: ignore[misc]
313337

314-
element_arg_type = get_argtype_by_name(ctx, 'element')
338+
element_arg_type = get_proper_type(get_argtype_by_name(ctx, 'element'))
315339

316340
if element_arg_type is not None and isinstance(element_arg_type, Instance):
317-
if element_arg_type.type.has_base(CLAUSE_ELEMENT_NAME) and not \
318-
element_arg_type.type.has_base(COLUMN_ELEMENT_NAME):
341+
if (element_arg_type.type.has_base(CLAUSE_ELEMENT_NAME) and not
342+
element_arg_type.type.has_base(COLUMN_ELEMENT_NAME)):
319343
return ctx.default_return_type.copy_modified(args=[NoneTyp()])
320344
return ctx.default_return_type
321345

@@ -339,12 +363,12 @@ class User(Base):
339363
This also tries to infer the type argument for 'RelationshipProperty'
340364
using the 'uselist' flag.
341365
"""
342-
assert isinstance(ctx.default_return_type, Instance)
366+
assert isinstance(ctx.default_return_type, Instance) # type: ignore[misc]
343367
original_type_arg = ctx.default_return_type.args[0]
344-
has_annotation = not isinstance(original_type_arg, UninhabitedType)
368+
has_annotation = not isinstance(get_proper_type(original_type_arg), UninhabitedType)
345369

346370
arg = get_argument_by_name(ctx, 'argument')
347-
arg_type = get_argtype_by_name(ctx, 'argument')
371+
arg_type = get_proper_type(get_argtype_by_name(ctx, 'argument'))
348372

349373
uselist_arg = get_argument_by_name(ctx, 'uselist')
350374

test/test-data/sqlalchemy-basics.test

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ from sqlalchemy import Column, Integer, String
1919
from sqlalchemy.sql.type_api import TypeEngine
2020

2121
from typing import TypeVar, Optional
22-
T = TypeVar('T', bound=int)
22+
T = TypeVar('T', bound=Optional[int])
2323

24-
def func(tp: TypeEngine[Optional[T]]) -> T: ...
24+
def func(tp: TypeEngine[T]) -> T: ...
2525
reveal_type(func(Integer())) # N: Revealed type is 'builtins.int*'
2626
func(String()) # E: Value of type variable "T" of "func" cannot be "str"
2727
[out]
@@ -143,3 +143,24 @@ class Other(Base):
143143
other: Other
144144
session.query(User).filter(User.other == other)
145145
[out]
146+
147+
[case testColumnFieldsDeclaredAliases]
148+
from sqlalchemy.ext.declarative import declarative_base
149+
from sqlalchemy import Column, Integer, String
150+
151+
Base = declarative_base()
152+
153+
CI = Column[int]
154+
CS = Column[str]
155+
156+
class User(Base):
157+
id: CI
158+
name: CS
159+
160+
user: User
161+
reveal_type(user.id) # N: Revealed type is 'builtins.int*'
162+
reveal_type(User.name) # N: Revealed type is 'sqlalchemy.sql.schema.Column[builtins.str*]'
163+
User(id=1)
164+
User(id='no') # E: Incompatible type for "id" of "User" (got "str", expected "int")
165+
User(undefined=0) # E: Unexpected column "undefined" for model "User"
166+
[out]

test/test-data/sqlalchemy-sql-schema.test

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ user_preference = Table('user_preference', metadata,
5252
[out]
5353

5454
[case testColumnWithForeignKeyDeclarative]
55+
from typing import Optional
56+
5557
from sqlalchemy.ext.declarative import declarative_base
5658
from sqlalchemy import Column, ForeignKey
5759
Base = declarative_base()
5860

5961
class Mytable(Base):
6062
__tablename__ = 'mytable'
61-
objid = Column(ForeignKey('othertable.objid'), index=True)
63+
objid: Column[Optional[int]] = Column(ForeignKey('othertable.objid'), index=True)
6264
[out]
6365

6466
[case testTableWithIndexes]

test/testsql.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
4040
mypy_cmdline = [
4141
'--show-traceback',
4242
'--no-silence-site-packages',
43+
'--no-error-summary',
4344
'--config-file={}/sqlalchemy.ini'.format(inipath),
4445
]
4546
py2 = testcase.name.lower().endswith('python2')

0 commit comments

Comments
 (0)