Skip to main content

A flake8 plugin for managing type-checking imports & forward references

Project description

Package version Code coverage Test status Supported Python versions Checked with mypy

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"

More examples can be found in the examples section.


If you're using pydantic, fastapi, or cattrs 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 application import into a type-checking block
TC002 Move third-party import into a type-checking block
TC002 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 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_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

Pydantic support base-class passlist

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.

  • name: type-checking-pydantic-enabled-baseclass-passlist
  • type: list
[flake8]
type-checking-pydantic-enabled-baseclass-passlist = NamedTuple, TypedDict  # default []

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 will also enable 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, unless you enable dependency support as described below.

FastAPI dependency support

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.

  • name: type-checking-fastapi-dependency-support-enabled
  • type: bool
[flake8]
type-checking-fastapi-dependency-support-enabled: true  # default false

Cattrs support

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.

  • name: type-checking-cattrs-enabled
  • type: bool
[flake8]
type-checking-cattrs-enabled = true  # default false

Rationale

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.

Examples

Performance example

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 example

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flake8-type-checking-2.0.0.tar.gz (21.1 kB view details)

Uploaded Source

Built Distribution

flake8_type_checking-2.0.0-py3-none-any.whl (18.3 kB view details)

Uploaded Python 3

File details

Details for the file flake8-type-checking-2.0.0.tar.gz.

File metadata

  • Download URL: flake8-type-checking-2.0.0.tar.gz
  • Upload date:
  • Size: 21.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.13 CPython/3.9.13 Linux/5.13.0-1029-azure

File hashes

Hashes for flake8-type-checking-2.0.0.tar.gz
Algorithm Hash digest
SHA256 6cc83291529d01799870220d6981c2c0ab4be29673f5bb651d78ad16cfa7169d
MD5 5a358adc2208109b2f9ec22b9c82c84f
BLAKE2b-256 8bdf4d2feaa1961c5ceb955db942943ff914a80bd38d519950493369d06fd473

See more details on using hashes here.

File details

Details for the file flake8_type_checking-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for flake8_type_checking-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d84b387e04c3dcc5d83ec632038f3cf119fdd2677708e480b61097844571ef2b
MD5 416d859f194f6fe34209223f9a2755cf
BLAKE2b-256 4243fa80ddd0164a1d42b6041f03888edc554e65d89226c72c73b781548fc0e0

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page