Repository: python/mypy

Issue:
Allow using # type: ignore to skip type checking in a file
As per PEP 484 discussion, it should be possible to use `# type: ignore` to skip type checking in the rest of a file:

```
# type: ignore
... # ignore type errors here
```

Currently the comment can only be used to ignore errors on individual lines.



Discussion:
(Moved assignment and Milestone from the duplicate #964.)

I wonder what we should do if this happens within a function. Should we then ignore all errors within the function only? Example:

```
def f() -> None:
    # type: ignore
    return 1 + 'x'   # don't report an error?

a = 1 + 'y'   # report an error here?
```

This could be nice, as it would let people annotate functions but ignore errors in the body of the function. `@no_type_check` is not a good match, as it also causes the annotations to be ignored.

Yeah, I agree that this should be block-scoped.

Hello,

Alternatively, could there be a way to exclude certain files or directories from command line or in the configuration file?

Currently, if I type `mypy my_directory`, the entire folder is scanned. It would be very handy to be able to add an `--exclude` argument.

In my case, I want to exclude automatically created Django migration files, but still scan my entire Django app.
You can use the `ignore_errors` per-file option.

Thank you @gvanrossum, this seems to be what I was looking for.

Could you please assist me a bit further? I tried to follow [the instructions](http://mypy.readthedocs.io/en/latest/config_file.html), but something is still not working:

**mypy.ini**
```
[mypy]

[gw_box.migrations.*]
ignore_errors = True
```

```console
$ mypy gw_box --ignore-missing-imports --config-file mypy.ini
gw_box/migrations/0001_initial.py:14: error: Need type annotation for variable
```
You need to add a section like this:
```
[mypy-dropbox.edgestore.datatypes.namespace]
ignore_errors = False
```
For docs see http://mypy.readthedocs.io/en/latest/config_file.html
Thank you, I figured out I was just missing the `mypy-` part.

P.S. Just figured out who you were. Huge thank you for creating Python :)
@ddfisher Are you planning to work on this?
I would love to see this feature get implemented.  In my tests I'm deliberately trying to break things, so being able to ignore the whole file is very useful.  I'm currently using mypy.ini, but I'd much rather prefer adding a comment at the top of the file.
Sorry we dropped the ball here -- we're a small team with many tasks. Do you want to try submitting a PR?
Should there also be a `#type: enable`? This would be similar to pylint's `disable`/`enable` to mark arbitrary blocks of code as not type-checked.
That's not in the PEP, and presumably that should be done using one of the decorators already defined by the PEP for that purpose.
`@no_type_check` at the class/function level probably suffices -- so `#type: ignore` by itself would only be used at the file level. Perhaps make it explicit by `#type: ignore file`.
I think we are pretty set on the syntax, as we don't want to make the parser implementation any more complicated for a special case. `# type: ignore` at the beginning of a block seems pretty straight forward to not type check that block.
Hello folks,

I've started using mypy (quite happily) but there are a few checks that I would like to ignore while not ignoring the entire file. It's not clear to me whether this is actually feasible as of now.

For instance, I am using SQLlchemy and create model classes such as:

```
class Workspace(db.Model):
```

With this, mypy complains that  `db.Model` is an invalid base class. Whether this is a valid message or not is one thing, but being able to ignore this message would be handy because it makes my experience more complicated since the file containing such definitions are always considered with problems.

I'd appreciate some feedback (might be doing something wrong here).

Cheers,
-Sylvain
@Lawouach SQLAlchemy is special, since it uses a lot of dynamic magic. We are working on better support for it. For now you can just place `# type: ignore` on the class definition line(s) where mypy report errors.
Fantastic! I thought this was for the entire file but it's for the block only, right?
A `# type: ignore` comment on a line currently suppresses _only_ errors reported on this particular line. This issue is about adding a second option of ignoring a whole piece of file. 
Gotcha! Thank you :)
So is it still not possible to do this? I'm looking to disable mypy checking for tests and cannot find a good way to do this.
@Beefy-Swain You can. See https://mypy.readthedocs.io/en/latest/config_file.html.
I’m currently working on a PR for this. I should have something up in the next couple of days.

Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -2,7 +2,7 @@
 import sys
 
 from typing import (
-    Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List, overload
+    Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List, overload, Set
 )
 MYPY = False
 if MYPY:
@@ -258,7 +258,7 @@ def __init__(self,
         self.is_stub = is_stub
         self.errors = errors
 
-        self.extra_type_ignores = []  # type: List[int]
+        self.type_ignores = set()  # type: Set[int]
 
         # Cache of visit_X methods keyed by type of visited object
         self.visitor_cache = {}  # type: Dict[type, Callable[[Optional[AST]], Any]]
@@ -294,11 +294,29 @@ def translate_expr_list(self, l: Sequence[AST]) -> List[Expression]:
             res.append(exp)
         return res
 
-    def translate_stmt_list(self, l: Sequence[AST]) -> List[Statement]:
+    def get_lineno(self, node: Union[ast3.expr, ast3.stmt]) -> int:
+        if (isinstance(node, (ast3.AsyncFunctionDef, ast3.ClassDef, ast3.FunctionDef))
+                and node.decorator_list):
+            return node.decorator_list[0].lineno
+        return node.lineno
+
+    def translate_stmt_list(self,
+                            stmts: Sequence[ast3.stmt],
+                            ismodule: bool = False) -> List[Statement]:
+        # A "# type: ignore" comment before the first statement of a module
+        # ignores the whole module:
+        if (ismodule and stmts and self.type_ignores
+                and min(self.type_ignores) < self.get_lineno(stmts[0])):
+            self.errors.used_ignored_lines[self.errors.file].add(min(self.type_ignores))
+            block = Block(self.fix_function_overloads(self.translate_stmt_list(stmts)))
+            block.is_unreachable = True
+            return [block]
+
         res = []  # type: List[Statement]
-        for e in l:
-            stmt = self.visit(e)
-            res.append(stmt)
+        for stmt in stmts:
+            node = self.visit(stmt)
+            res.append(node)
+
         return res
 
     op_map = {
@@ -403,13 +421,12 @@ def translate_module_id(self, id: str) -> str:
         return id
 
     def visit_Module(self, mod: ast3.Module) -> MypyFile:
-        body = self.fix_function_overloads(self.translate_stmt_list(mod.body))
-        ignores = [ti.lineno for ti in mod.type_ignores]
-        ignores.extend(self.extra_type_ignores)
+        self.type_ignores = {ti.lineno for ti in mod.type_ignores}
+        body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True))
         return MypyFile(body,
                         self.imports,
                         False,
-                        set(ignores),
+                        self.type_ignores,
                         )
 
     # --- stmt ---
@@ -615,7 +632,7 @@ def make_argument(self, arg: ast3.arg, default: Optional[ast3.expr], kind: int,
             elif type_comment is not None:
                 extra_ignore, arg_type = parse_type_comment(type_comment, arg.lineno, self.errors)
                 if extra_ignore:
-                    self.extra_type_ignores.append(arg.lineno)
+                    self.type_ignores.add(arg.lineno)
 
         return Argument(Var(arg.arg), arg_type, self.visit(default), kind)
 
@@ -673,7 +690,7 @@ def visit_Assign(self, n: ast3.Assign) -> AssignmentStmt:
         if n.type_comment is not None:
             extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             typ = None
         s = AssignmentStmt(lvalues, rvalue, type=typ, new_syntax=False)
@@ -707,7 +724,7 @@ def visit_For(self, n: ast3.For) -> ForStmt:
         if n.type_comment is not None:
             extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             target_type = None
         node = ForStmt(self.visit(n.target),
@@ -722,7 +739,7 @@ def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt:
         if n.type_comment is not None:
             extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             target_type = None
         node = ForStmt(self.visit(n.target),
@@ -753,7 +770,7 @@ def visit_With(self, n: ast3.With) -> WithStmt:
         if n.type_comment is not None:
             extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             target_type = None
         node = WithStmt([self.visit(i.context_expr) for i in n.items],
@@ -767,7 +784,7 @@ def visit_AsyncWith(self, n: ast3.AsyncWith) -> WithStmt:
         if n.type_comment is not None:
             extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             target_type = None
         s = WithStmt([self.visit(i.context_expr) for i in n.items],
diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py
--- a/mypy/fastparse2.py
+++ b/mypy/fastparse2.py
@@ -16,7 +16,7 @@
 """
 import sys
 
-from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List
+from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List, Set
 MYPY = False
 if MYPY:
     import typing  # for typing.Type, which conflicts with types.Type
@@ -163,7 +163,7 @@ def __init__(self,
         # Cache of visit_X methods keyed by type of visited object
         self.visitor_cache = {}  # type: Dict[type, Callable[[Optional[AST]], Any]]
 
-        self.extra_type_ignores = []  # type: List[int]
+        self.type_ignores = set()  # type: Set[int]
 
     def fail(self, msg: str, line: int, column: int, blocker: bool = True) -> None:
         if blocker or not self.options.ignore_errors:
@@ -193,12 +193,28 @@ def translate_expr_list(self, l: Sequence[AST]) -> List[Expression]:
             res.append(exp)
         return res
 
-    def translate_stmt_list(self, l: Sequence[AST]) -> List[Statement]:
+    def get_lineno(self, node: Union[ast27.expr, ast27.stmt]) -> int:
+        if isinstance(node, (ast27.ClassDef, ast27.FunctionDef)) and node.decorator_list:
+            return node.decorator_list[0].lineno
+        return node.lineno
+
+    def translate_stmt_list(self,
+                            stmts: Sequence[ast27.stmt],
+                            module: bool = False) -> List[Statement]:
+        # A "# type: ignore" comment before the first statement of a module
+        # ignores the whole module:
+        if (module and stmts and self.type_ignores
+                and min(self.type_ignores) < self.get_lineno(stmts[0])):
+            self.errors.used_ignored_lines[self.errors.file].add(min(self.type_ignores))
+            block = Block(self.fix_function_overloads(self.translate_stmt_list(stmts)))
+            block.is_unreachable = True
+            return [block]
+
         res = []  # type: List[Statement]
-        for e in l:
-            stmt = self.visit(e)
-            assert isinstance(stmt, Statement)
-            res.append(stmt)
+        for stmt in stmts:
+            node = self.visit(stmt)
+            assert isinstance(node, Statement)
+            res.append(node)
         return res
 
     op_map = {
@@ -304,13 +320,12 @@ def translate_module_id(self, id: str) -> str:
         return id
 
     def visit_Module(self, mod: ast27.Module) -> MypyFile:
+        self.type_ignores = {ti.lineno for ti in mod.type_ignores}
         body = self.fix_function_overloads(self.translate_stmt_list(mod.body))
-        ignores = [ti.lineno for ti in mod.type_ignores]
-        ignores.extend(self.extra_type_ignores)
         return MypyFile(body,
                         self.imports,
                         False,
-                        set(ignores),
+                        self.type_ignores,
                         )
 
     # --- stmt ---
@@ -558,7 +573,7 @@ def visit_Assign(self, n: ast27.Assign) -> AssignmentStmt:
             extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors,
                                                    assume_str_is_unicode=self.unicode_literals)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
 
         stmt = AssignmentStmt(self.translate_expr_list(n.targets),
                               self.visit(n.value),
@@ -578,7 +593,7 @@ def visit_For(self, n: ast27.For) -> ForStmt:
             extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors,
                                                    assume_str_is_unicode=self.unicode_literals)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             typ = None
         stmt = ForStmt(self.visit(n.target),
@@ -608,7 +623,7 @@ def visit_With(self, n: ast27.With) -> WithStmt:
             extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors,
                                                    assume_str_is_unicode=self.unicode_literals)
             if extra_ignore:
-                self.extra_type_ignores.append(n.lineno)
+                self.type_ignores.add(n.lineno)
         else:
             typ = None
         stmt = WithStmt([self.visit(n.context_expr)],


Test patch:
diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test
--- a/test-data/unit/check-ignore.test
+++ b/test-data/unit/check-ignore.test
@@ -218,3 +218,43 @@ def f() -> None: pass
 
 [case testCannotIgnoreBlockingError]
 yield  # type: ignore  # E: 'yield' outside function
+
+[case testIgnoreWholeModule1]
+# flags: --warn-unused-ignores
+# type: ignore
+IGNORE # type: ignore  # E: unused 'type: ignore' comment
+
+[case testIgnoreWholeModule2]
+# type: ignore
+if True:
+    IGNORE
+
+[case testIgnoreWholeModule3]
+# type: ignore
+@d
+class C: ...
+IGNORE
+
+[case testIgnoreWholeModule4]
+# type: ignore
+@d
+
+def f(): ...
+IGNORE
+
+[case testDontIgnoreWholeModule1]
+if True:
+    # type: ignore
+    ERROR # E: Name 'ERROR' is not defined
+ERROR # E: Name 'ERROR' is not defined
+
+[case testDontIgnoreWholeModule2]
+@d  # type: ignore
+class C: ...
+ERROR # E: Name 'ERROR' is not defined
+
+[case testDontIgnoreWholeModule3]
+@d  # type: ignore
+
+def f(): ...
+ERROR # E: Name 'ERROR' is not defined