mypy
mypy copied to clipboard
Ignore typing errors in individual blocks
It is already possible to silence errors using # type: ignore
on individual lines, and - since #626) also for a whole file.
Additionally it would be nice to ignore errors on individual blocks. Especially on a class/function level when abstracting away an untyped library. For example:
class MyAbstraction:
def __init__(self, url: str) -> None:
self.hostname = url.host # <-- I want this to be caught by the type-checker
def some_call(self) -> int:
# type: ignore
result = api.untyped_call()
return result
def another_large_call(self) -> int:
# type: ignore
result_1 = api.untyped_call_1()
result_2 = api.untyped_call_2()
result = result_1 + result_2
return result
When running mypy
in "strict" mode the code will be littered with messages like Call to an untyped function ... in a typed context
. Currently the only options are:
- Add
# type: ignore
on each line causing errors by the external library- Disadvantage: Cumbersome (many type-comments needed)
- Advantage: Only the problematic calls are silenced
- Add
# type: ignore
to the file- Disadvantage: Typing errors unrelated to the external library become invisible
- Advantage: Only one line to add
The second option is best used if a module contains only calls to the external library. For new projects this is doable, but older/larger code-bases often need some refactoring for this. And this can be error-prone and seems risky to do only for type-hinting.
Having the option to disable errors on either a class-level or block-level would add a lot of flexibility.
See #6830 (the PR that closed #626). It’s not currently possible to get the line number of the colon that immediately precedes an indented block from the AST alone... at least not without resorting to error-prone guessing. We can't reliably tell the difference between this:
def f() -> (
None
): # type: ignore
...
And this:
def f() -> (
None):
# type: ignore
...
So I think block-scoped ignores using a # type: ignore
comment on the first line are not very likely. Perhaps there is another way, like @no_type_check
, but with preserved annotations in the function signature.
I wanted this also, but I don't think it's worth changing mypy's source parsing 🙁.
You can use the @typing.no_type_check
decorator for this.
@typing.no_type_check
works perfectly (thanks @JelleZijlstra).
Why is this issue still open?
The original issue asked for something slightly different (block-scoped type-ignore), which is a bit different from @no_type_check
, which is always function-scoped.
I'm not sure block-scoped type-ignores are all that necessary, but it's a reasonable request.
Try with:
from typing import Any, cast
import api as untype_api # It is import to use a different name to untyped module
api = cast(Any, untype_api)
class MyAbstraction:
def some_call(self) -> int:
result = api.untyped_call() # this shouldn't show type warnings.
return result
It would be nice if @typing.no_type_check would be also a class decorator
It would be nice if @typing.no_type_check would be also a class decorator
@jentyk According to the docs it should work as well as a class decorator, but it isn't apparently true. https://docs.python.org/3/library/typing.html#typing.no_type_check
Mentioned here https://github.com/python/mypy/issues/607.
pylint supports this type of pragma, so it is clearly theoretically possible.
Also no_type_check
only takes a single argument for the decorated object. There is no way to limit the ignore to one particular class of error. For example. there is no way to apply
# type: ignore[misc]
to a function. You have to ignore everything or write a separate comment for each instance. And if you add a new decorator for this purpose, then using it would mean adding a runtime dependency to mypy just for that.
What about introducing two comments to disable and enable specific rule(s) between them? For example:
class MyAbstraction:
def __init__(self, url: str) -> None:
self.hostname = url.host
def large_call(self) -> int:
# type: off[no-untyped-call]
result_1 = api.untyped_call_1()
result_2 = api.untyped_call_2()
result_3 = api.untyped_call_2()
result_4 = api.untyped_call_2()
result = result_1 + result_2 + result_3 + result_4
return result
# type: on[no-untyped-call]
This is also similar to formatters such as Black which uses # fmt: off
and # fmt: on
comments. It's still not disabling specific rule for a whole block with one comment, but two comments (at the start and the end) for a function are still better than having to put a comment on every line with typing error, especially on larger functions.
What about having something like typing.no_type_check()
be usable as a context manager?
For instance:
import typing
class MyAbstraction:
...
def another_large_call(self) -> int:
x : int = f() # This is checked
with typing.no_type_check: # no lines inside this context are checked
result_1 = api.untyped_call_1()
result_2 = api.untyped_call_2()
result = result_1 + result_2
y: str = g() # this is also checked
return result
I don't like the context-manager idea. Type-hints are just hints with no behaviour during runtime. They are PEP-3107 "annotations". The with
block is a runtime construct. And mixing this feels wrong. The annotations can be extracted by static analysis tools but should have no impact on the runtime. When we add a "with" block they will trigger runtime behaviour. Even if typing.no_type_check
is part of the standard library it could still cause unexpected side-effects during runtime.
I initially thought about a with
block as well, as it just "looks right" when looking at the code. But I dismissed it because it mixes runtime with static-analysis.
This is a "gut-feeling" and may be subjective as I have no concrete proof that this can cause problems. If there are any, I'd be happy to learn.
Another case...
I have code like this:
class TurnLogAdmin(ModelView, model=TurnLog):
column_list = [
TurnLog.id,
TurnLog.turn_id,
TurnLog.timestamp,
TurnLog.key,
] # type: ignore
Due to issues between SQLModel (which mashes SQL Alchemy and Pydantic models together, resulting in difficult typing situation) and sqladmin (whose typings expect SQL Alchemy model fields here)... mypy generates a type error for each element of the list:
src/server/app.py:60: error: List item 0 has incompatible type "Optional[int]"; expected "Union[str, InstrumentedAttribute]" [list-item]
src/server/app.py:61: error: List item 1 has incompatible type "int"; expected "Union[str, InstrumentedAttribute]" [list-item]
src/server/app.py:62: error: List item 2 has incompatible type "datetime"; expected "Union[str, InstrumentedAttribute]" [list-item]
I was hoping the # type: ignore
on the last line would cover the whole list literal, but it has no effect. Since there is no block-ignore syntax it seems like I need an ignore comment for each line individually.
FWIW https://github.com/python/mypy/pull/6648 and https://github.com/python/mypy/issues/1032 seem to intend that the ignore comment would cover the whole expression
but perhaps multi-line list literal is a different construct? (I'm not sure exactly what does and doesn't count as an 'expression' ... https://docs.python.org/3/reference/expressions.html seems to include list literals though)
For those who wants this for the --disallow-untyped-calls
problems: https://github.com/python/mypy/pull/15845 added a new flag to not show this error for calls to functions/methods from specific packages/modules/classes (will be available in next mypy release).
A solution for blocks or class annotation would come in handy. Mypy can't handle things going on inside django ORM constraint definitions.
It would be great if there's start and stop comments to pause mypy's type check, like
# mypy: off
...
# mypy: on
cuz i do need to exclude only a block of code (i.e., an if block)