Lets you know which imports to move in or out of type-checking blocks.
The plugin assumes that the imports you only use for type hinting are not required at runtime. When imports aren't strictly required at runtime, it means we can guard them.
Guarding imports provides 3 major benefits:
Essentially, this code:
import pandas # 15mb library
x: pandas.DataFrame
becomes this:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import pandas # <-- no longer imported at runtime
x: "pandas.DataFrame"
More examples can be found in the examples section.
If you're using pydantic, fastapi, cattrs, or injector see the configuration for how to enable support.
The plugin will:
And depending on which error code range you've opted into, it will tell you
from __future__ import annotations
importCode | Description |
---|---|
TC001 | Move application import into a type-checking block |
TC002 | Move third-party import into a type-checking block |
TC003 | Move built-in import into a type-checking block |
TC004 | Move import out of type-checking block. Import is used for more than type hinting. |
TC005 | Found empty type-checking block |
TC006 | Annotation in typing.cast() should be a string literal |
TC007 | Type alias needs to be made into a string literal |
TC008 | Type alias does not need to be a string literal |
TC009 | Move declaration out of type-checking block. Variable is used for more than type hinting. |
TC010 | Operands for | cannot be a string literal |
You need to choose whether to opt-into using the
TC100
- or the TC200
-range of error codes.
They represent two different ways of solving the same problem, so please only choose one.
TC100
and TC101
manage forward references by taking advantage of
postponed evaluation of annotations.
Code | Description |
---|---|
TC100 | Add 'from __future__ import annotations' import |
TC101 | Annotation does not need to be a string literal |
TC200
and TC201
manage forward references using string literals.
Code | Description |
---|---|
TC200 | Annotation needs to be made into a string literal |
TC201 | Annotation does not need to be a string literal |
Add TC
and TC1
or TC2
to your flake8 config like this:
[flake8]
max-line-length = 80
max-complexity = 12
...
ignore = E501
# You can use 'extend-select' (new in flake8 v4):
extend-select = TC, TC2
# OR 'select':
select = C,E,F..., TC, TC2 # or TC1
# OR 'enable-extensions':
enable-extensions = TC, TC2 # or TC1
If you are unsure which TC
range to pick, see the rationale for more info.
pip install flake8-type-checking
These options are configurable, and can be set in your flake8 config.
If you wish to exempt certain modules from needing to be moved into type-checking blocks, you can specify which modules to ignore.
type-checking-exempt-modules
list
[flake8]
type-checking-exempt-modules = typing_extensions # default []
The plugin, by default, will report TC00[1-3] errors for imports if there aren't already other imports from the same module. When there are other imports from the same module, the import circularity and performance benefits no longer apply from guarding an import.
When strict mode is enabled, the plugin will flag all imports that can be moved.
type-checking-strict
bool
[flake8]
type-checking-strict = true # default false
If you use Pydantic models in your code, you should enable Pydantic support. This will treat any class variable annotation as being needed during runtime.
type-checking-pydantic-enabled
bool
[flake8]
type-checking-pydantic-enabled = true # default false
Disabling checks for all class annotations is a little aggressive.
If you feel comfortable that all base classes named, e.g., NamedTuple
are not Pydantic models,
then you can pass the names of the base classes in this setting, to re-enable checking for classes
which inherit from them.
type-checking-pydantic-enabled-baseclass-passlist
list
[flake8]
type-checking-pydantic-enabled-baseclass-passlist = NamedTuple, TypedDict # default []
If you're using the plugin for a FastAPI project, you should enable support. This will treat the annotations of any decorated function as needed at runtime.
Enabling FastAPI support will also enable Pydantic support.
type-checking-fastapi-enabled
bool
[flake8]
type-checking-fastapi-enabled = true # default false
One more thing to note for FastAPI users is that dependencies
(functions used in Depends
) will produce false positives, unless
you enable dependency support as described below.
In addition to preventing false positives for decorators, we can prevent false positives for dependencies. We are making a pretty bad trade-off however: by enabling this option we treat every annotation in every function definition across your entire project as a possible dependency annotation. In other words, we stop linting all function annotations completely, to avoid the possibility of false positives. If you prefer to be on the safe side, you should enable this - otherwise it might be enough to be aware that false positives can happen for functions used as dependencies.
Enabling dependency support will also enable FastAPI and Pydantic support.
type-checking-fastapi-dependency-support-enabled
bool
[flake8]
type-checking-fastapi-dependency-support-enabled = true # default false
If you're using SQLAlchemy 2.0+, you can enable support.
This will treat any Mapped[...]
types as needed at runtime.
It will also specially treat the enclosed type, since it may or may not
need to be available at runtime depending on whether or not the enclosed
type is a model or not, since models can have circular dependencies.
type-checking-sqlalchemy-enabled
bool
type-checking-sqlalchemy-enabled = true # default false
Since it's possible to create subclasses of sqlalchemy.orm.Mapped
that
define some custom behavior for the mapped attribute, but otherwise still
behave like any other mapped attribute, i.e. the same runtime restrictions
apply it's possible to provide additional dotted names that should be treated
like subclasses of Mapped
. By default we check for sqlalchemy.orm.Mapped
,
sqlalchemy.orm.DynamicMapped
and sqlalchemy.orm.WriteOnlyMapped
.
If there's more than one import path for the same Mapped
subclass, then you
need to specify each of them as a separate dotted name.
type-checking-sqlalchemy-mapped-dotted-names
list
type-checking-sqlalchemy-mapped-dotted-names = a.MyMapped, a.b.MyMapped # default []
If you're using the plugin in a project which uses cattrs
,
you can enable support. This will treat the annotations
of any decorated attrs
class as needed at runtime, since
cattrs.unstructure
calls will fail when loading
classes where types are not available at runtime.
Note: the cattrs support setting does not yet detect and ignore class var annotations on dataclasses or other non-attrs class types. This can be added in the future if needed.
type-checking-cattrs-enabled
bool
[flake8]
type-checking-cattrs-enabled = true # default false
If you're using the injector library, you can enable support.
This will treat any Inject[Dependency]
types as needed at runtime.
type-checking-injector-enabled
bool
type-checking-injector-enabled = true # default false
Why did we create this plugin?
Good type hinting typically requires a lot of project imports, which can increase
the risk of import cycles
in a project. The recommended way of preventing this problem is to use typing.TYPE_CHECKING
blocks
to guard these types of imports. In particular, TC001
helps protect against this issue.
Once imports are guarded, they will no longer be evaluated/imported during runtime. The consequence of this is that these imports can no longer be treated as if they were imported outside the block. Instead we need to use forward references.
For Python version >= 3.7
, there are actually two ways of solving this issue.
You can either make your annotations string literals, or you can use a __futures__
import to enable postponed evaluation of annotations.
See this excellent stackoverflow answer
for a great explanation of the differences.
You can run this flake8 plugin as a pre-commit hook:
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
additional_dependencies:
- flake8-type-checking
Please feel free to open an issue or a PR ๐