A flake8 plugin for managing type-checking imports & forward references
Reason this release was yanked:
Shipped with bug
Project description
flake8-type-checking
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.
This provides 3 major benefits:
- 🔧 It reduces import circularity issues,
- 🧹 It organizes imports, and
- 🚀 It completely eliminates the overhead of type hint imports at runtime
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"
If you're using pydantic or fastapi, see the configuration for how to enable support.
Primary features
The plugin will:
- Tell you when an import should be moved into a type-checking block
- Tell you when an import should be moved out again
And depending on which error code range you've opted into, it will tell you
- Whether you need to add a
from __future__ import annotations
import - Whether you need to quote an annotation
- Whether you can unquote a quoted annotation
Error codes
Code | Description |
---|---|
TC001 | Move import into a type-checking block |
TC002 | Move third-party import into a type-checking block |
TC003 | Found multiple type checking blocks |
TC004 | Move import out of type-checking block. Import is used for more than type hinting. |
TC005 | Empty type-checking block |
Choosing how to handle forward references
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 |
Enabling error ranges
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 '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.
Installation
pip install flake8-type-checking
Configuration
These options are configurable, and can be set in your flake8 config.
Exempt modules
If you wish to exempt certain modules from needing to be moved into type-checking blocks, you can specify which modules to ignore.
- setting name:
type-checking-exempt-modules
- type:
list
[flake8]
type-checking-exempt-modules: typing, typing_extensions # default []
Pydantic support
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.
- name:
type-checking-pydantic-enabled
- type:
bool
[flake8]
type-checking-pydantic-enabled: true # default false
FastAPI support
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 also enabled Pydantic support.
- name:
type-checking-fastapi-enabled
- type:
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.
We cannot detect which functions are used as dependencies,
and guarding type hint-imports for these functions will
crash your app at runtime if you do so. It is therefore important
to know about this ahead of time.
If you have a good suggestion for how to resolve this issue, please feel free to submit an issue or a PR.
Rationale
Why did we create this plugin?
Good type hinting requires a lot of imports, which can increase
the risk of import cycles
in your project. The recommended way of preventing this problem is
to use typing.TYPE_CHECKING
blocks to guard these types of imports.
Both TC001
and TC002
help alleviate this problem; the reason there are two
codes instead of one, is because the import cycles rarely occur from
library/third-party imports, so this artificial split provides a way to filter down
the total pool of imports for users that want to guard against import cycles,
but don't want to manage every import in their projects this strictly.
Once imports are guarded, they will no longer be evaluated 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 better explanation of the differences.
Examples
Performance
Imports for type hinting can have a performance impact.
import pandas
def dataframe_length(df: pandas.DataFrame) -> int:
return len(df)
In this example, we import a 15mb library, for a single type hint.
We don't need to perform this operation at runtime, at all. If we know that the import will not otherwise be needed by surrounding code, we can simply guard it, like this:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import pandas # <-- no longer imported at runtime
def dataframe_length(df: pandas.DataFrame) -> int:
return len(df)
Now the import is no longer made at runtime. If you're unsure about how this works, see the mypy docs for a basic introduction.
Import circularity
Bad code
models/a.py
from models.b import B
class A(Model):
def foo(self, b: B): ...
models/b.py
from models.a import A
class B(Model):
def bar(self, a: A): ...
Will result in these errors
>> a.py: TC002 Move third-party import 'models.b.B' into a type-checking block
>> b.py: TC002 Move third-party import 'models.a.A' into a type-checking block
and consequently trigger these errors if imports are purely moved into type-checking block, without proper forward reference handling
>> a.py: TC100 Add 'from __future__ import annotations' import
>> b.py: TC100 Add 'from __future__ import annotations' import
or
>> a.py: TC200 Annotation 'B' needs to be made into a string literal
>> b.py: TC200 Annotation 'A' needs to be made into a string literal
Good code
models/a.py
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models.b import B
class A(Model):
def foo(self, b: 'B'): ...
models/b.py
# TC1
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models.a import A
class B(Model):
def bar(self, a: A): ...
or
# TC2
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models.a import A
class B(Model):
def bar(self, a: 'A'): ...
Running the plugin as a pre-commit hook
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
Contributing
Please feel free to open an issue or a PR 👏
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for flake8-type-checking-1.3.0.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 679394529df5e046d541101c6df7c5b051df4afbfc94c68b00988173b8f8cc6b |
|
MD5 | 52597a652f11dbb6d5efe16707765a5a |
|
BLAKE2b-256 | 5769183951e520b764a59cdc20bc9096f08823d94b2a6c9de5f62abdf6968352 |
Hashes for flake8_type_checking-1.3.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e41f1713632fc42030c72b2330e53d176972d23ecf2a1e4901c8d1f9ce0f4b47 |
|
MD5 | 9f5f4e1ba2fd33b1a6daf345dd1a1dfa |
|
BLAKE2b-256 | d5da8f54ba982a083176d635dd11a7ae604b3413ebb1708fc0fe841ead02b94e |