From d538df5fbb13186f7a1d39cc04b56f346ed7f4fc Mon Sep 17 00:00:00 2001 From: dmontagu <35119617+dmontagu@users.noreply.github.com> Date: Mon, 2 Dec 2019 05:09:37 -0800 Subject: [PATCH] Update for compatibility with mypy 0.750 (#1058) * Update for compatibility with mypy 0.750 * Remove coverage checks for 0.740 compatibility --- changes/1057-dmontagu.md | 1 + pydantic/mypy.py | 45 ++++++++++++++++++++++++++++++---------- tests/mypy/test_mypy.py | 6 +++--- tests/requirements.txt | 2 +- 4 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 changes/1057-dmontagu.md diff --git a/changes/1057-dmontagu.md b/changes/1057-dmontagu.md new file mode 100644 index 0000000..7e3392d --- /dev/null +++ b/changes/1057-dmontagu.md @@ -0,0 +1 @@ +Update mypy to version 0.750 \ No newline at end of file diff --git a/pydantic/mypy.py b/pydantic/mypy.py index e638626..dcfd85b 100644 --- a/pydantic/mypy.py +++ b/pydantic/mypy.py @@ -1,5 +1,5 @@ from configparser import ConfigParser -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type as TypingType +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type as TypingType, Union from mypy.errorcodes import ErrorCode from mypy.nodes import ( @@ -17,6 +17,7 @@ from mypy.nodes import ( Context, Decorator, EllipsisExpr, + FuncBase, FuncDef, JsonDict, MemberExpr, @@ -25,6 +26,7 @@ from mypy.nodes import ( PlaceholderNode, RefExpr, StrExpr, + SymbolNode, SymbolTableNode, TempNode, TypeInfo, @@ -47,6 +49,7 @@ from mypy.types import ( TypeVarDef, TypeVarType, UnionType, + get_proper_type, ) from mypy.typevars import fill_typevars from mypy.util import get_unique_redefinition_name @@ -78,7 +81,7 @@ class PydanticPlugin(Plugin): sym = self.lookup_fully_qualified(fullname) if sym and isinstance(sym.node, TypeInfo): # pragma: no branch # No branching may occur if the mypy cache has not been cleared - if any(base.fullname() == BASEMODEL_FULLNAME for base in sym.node.mro): + if any(get_fullname(base) == BASEMODEL_FULLNAME for base in sym.node.mro): return self._pydantic_model_class_maker_callback return None @@ -132,7 +135,7 @@ def from_orm_callback(ctx: MethodContext) -> Type: return ctx.default_return_type orm_mode = pydantic_metadata.get('config', {}).get('orm_mode') if orm_mode is not True: - error_from_orm(model_type.type.name(), ctx.api, ctx.context) + error_from_orm(get_name(model_type.type), ctx.api, ctx.context) return ctx.default_return_type @@ -168,7 +171,7 @@ class PydanticModelTransformer: if info[field.name].type is None: if not ctx.api.final_iteration: ctx.api.defer() - is_settings = any(base.fullname() == BASESETTINGS_FULLNAME for base in info.mro[:-1]) + is_settings = any(get_fullname(base) == BASESETTINGS_FULLNAME for base in info.mro[:-1]) self.add_initializer(fields, config, is_settings) self.add_construct_method(fields) self.set_frozen(fields, frozen=config.allow_mutation is False) @@ -203,7 +206,7 @@ class PydanticModelTransformer: continue # Each class depends on the set of fields in its ancestors - ctx.api.add_plugin_dependency(make_wildcard_trigger(info.fullname())) + ctx.api.add_plugin_dependency(make_wildcard_trigger(get_fullname(info))) for name, value in info.metadata[METADATA_KEY]['config'].items(): config.setdefault(name, value) return config @@ -275,7 +278,7 @@ class PydanticModelTransformer: superclass_fields = [] # Each class depends on the set of fields in its ancestors - ctx.api.add_plugin_dependency(make_wildcard_trigger(info.fullname())) + ctx.api.add_plugin_dependency(make_wildcard_trigger(get_fullname(info))) for name, data in info.metadata[METADATA_KEY]['fields'].items(): if name not in known_fields: @@ -357,8 +360,8 @@ class PydanticModelTransformer: var = field.to_var(info, use_alias=False) var.info = info var.is_property = frozen - var._fullname = info.fullname() + '.' + var.name() - info.names[var.name()] = SymbolTableNode(MDEF, var) + var._fullname = get_fullname(info) + '.' + get_name(var) + info.names[get_name(var)] = SymbolTableNode(MDEF, var) def get_config_update(self, substmt: AssignmentStmt) -> Optional['ModelConfigData']: """ @@ -396,7 +399,7 @@ class PydanticModelTransformer: expr = stmt.rvalue if isinstance(expr, TempNode): # TempNode means annotation-only, so only non-required if Optional - value_type = cls.info[lhs.name].type + value_type = get_proper_type(cls.info[lhs.name].type) if isinstance(value_type, UnionType) and any(isinstance(item, NoneType) for item in value_type.items): # Annotated as Optional, or otherwise having NoneType in the union return False @@ -618,7 +621,7 @@ def add_method( for arg in args: assert arg.type_annotation, 'All arguments must be fully typed.' arg_types.append(arg.type_annotation) - arg_names.append(arg.variable.name()) + arg_names.append(get_name(arg.variable)) arg_kinds.append(arg.kind) function_type = ctx.api.named_type('__builtins__.function') @@ -631,7 +634,7 @@ def add_method( func.type = set_callable_name(signature, func) func.is_class = is_classmethod # func.is_static = is_staticmethod - func._fullname = info.fullname() + '.' + name + func._fullname = get_fullname(info) + '.' + name func.line = info.line # NOTE: we would like the plugin generated node to dominate, but we still @@ -661,3 +664,23 @@ def add_method( info.names[name] = sym info.defn.defs.body.append(func) + + +def get_fullname(x: Union[FuncBase, SymbolNode]) -> str: + """ + Used for compatibility with mypy 0.740; can be dropped once support for 0.740 is dropped. + """ + fn = x.fullname + if callable(fn): # pragma: no cover + return fn() + return fn + + +def get_name(x: Union[FuncBase, SymbolNode]) -> str: + """ + Used for compatibility with mypy 0.740; can be dropped once support for 0.740 is dropped. + """ + fn = x.name + if callable(fn): # pragma: no cover + return fn() + return fn diff --git a/tests/mypy/test_mypy.py b/tests/mypy/test_mypy.py index 0445222..36cc5e5 100644 --- a/tests/mypy/test_mypy.py +++ b/tests/mypy/test_mypy.py @@ -52,9 +52,9 @@ def test_mypy_results(config_filename, python_filename, output_filename): # Specifying a different cache dir for each configuration dramatically speeds up subsequent execution # It also prevents cache-invalidation-related bugs in the tests cache_dir = f'.mypy_cache/test-{config_filename[:-4]}' - actual_result = mypy_api.run( - [full_filename, '--config-file', full_config_filename, '--cache-dir', cache_dir, '--show-error-codes'] - ) + command = [full_filename, '--config-file', full_config_filename, '--cache-dir', cache_dir, '--show-error-codes'] + print(f"\nExecuting: mypy {' '.join(command)}") # makes it easier to debug as necessary + actual_result = mypy_api.run(command) actual_out, actual_err, actual_returncode = actual_result # Need to strip filenames due to differences in formatting by OS actual_out = '\n'.join(['.py:'.join(line.split('.py:')[1:]) for line in actual_out.split('\n') if line]).strip() diff --git a/tests/requirements.txt b/tests/requirements.txt index 678dc58..067af18 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -4,7 +4,7 @@ Cython==0.29.14;sys_platform!='win32' flake8==3.7.9 flake8-quotes==2.1.1 isort==4.3.21 -mypy==0.740 +mypy==0.750 pycodestyle==2.5.0 pyflakes==2.1.1 pytest==5.3.1