Skip to content

TCH setting to autofix with conversion to string #5559

@smackesey

Description

@smackesey

Currently the TCH (flake8-type-checking) rules require annotations to be stored as strings (i.e. not evaluated). Annotations are stored a strings if:

  • The annotations are string literals (i.e. are quoted)
  • The annotations are in a module with from __future__ import annotations (which causes evaluation of all annotations in the module to be skipped)
  • The annotations are of local variables (the Python interpreter does not evaluate local variable annotations)

One approach to getting TCH autofix working is to inject from __future__ import annotations into all your source modules (using I002). This is not ideal because string annotations unavoidably break code that meets these conditions:

  1. annotations are being created in a local scope
  2. an annotation references a symbol only available in that local scope
  3. the annotation must be operated on at runtime

Here is an example of code meeting these conditions:

def make_bar():
    class Bar:
        ...

    def bar(x: "Bar"):
        ...
    return bar

bar = make_bar()

# `bar` has a string annotation-- if we want to operate on it, we need to evaluate it. This requires
# calling `get_type_hints`, but this fails because "Bar" can't be dereferenced since referent `Bar`
# vanished with local scope
get_type_hints(bar)  # => NameError: name 'Bar' is not defined.

We have plenty of code meeting these conditions in Dagster (mostly in tests), so my experiment in injecting from __future__ import annotations to enable TCH autofix has failed. Other projects that use runtime type annotation introspection are likely to encounter the same issues.

Therefore, a significant enhancement to TCH autofix would be to detect where annotations could be stored as strings, and perform a targeted conversion for you using quoting. Example:

from threading import Lock

def foo(lock: Lock):
    ...

After applying TCH autofix:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from threading import Lock

def foo(lock: "Lock"):
    ...

This would make TCH autofix a lot more useful for us and probably many other projects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreRelated to core functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions