ruff icon indicating copy to clipboard operation
ruff copied to clipboard

Unable to detect `runtime-evaluated-base-classes` across files

Open sujuka99 opened this issue 2 years ago • 4 comments

TCH001 seems to incorrectly trigger on the example below despite the settings only when from __future__ import annotations is present.

base_model.py

from pydantic import BaseModel

class Base(BaseModel):
    ...

field_model.py

from pydantic import BaseModel

class F(BaseModel):
    ...

example_model.py

from __future__ import annotations  # Remove to avoid the false positive

from minimal_example_ruff_tch.base_model import Base
from minimal_example_ruff_tch.field_model import F  # Falsely flagged by `TCH001`

class ExampleModel(Base):
    field: F

Running ruff check path/to/modules --select "TCH001" --fix results in the following:

example_model.py

from __future__ import annotations
from typing import TYPE_CHECKING
from minimal_example_ruff_tch.base_model import Base

if TYPE_CHECKING:
    from minimal_example_ruff_tch.field_model import F

class ExampleModel(Base):
    field: F

Removing the annotations import from example_model.py or putting Base and F in the same file (field_model.py for example) avoids the false positive.

Current Ruff settings:

[tool.ruff.flake8-type-checking]
runtime-evaluated-base-classes = ["pydantic.BaseModel"]

Current Ruff version: ruff 0.0.292

sujuka99 avatar Oct 09 '23 09:10 sujuka99

So, I think there are two things going on here.

The main issue is that we don't support making these kinds of inferences across files. So when you use from minimal_example_ruff_tch.base_model import Base and then class ExampleModel(Base):, Ruff is unable to detect that ExampleModel is a pydantic.BaseModel.

Instead, you'd need to add minimal_example_ruff_tch.base_model.Base to your runtime-evaluated-base-classes, or add pydantic.BaseModel as an additional subclass to ExampleModel (so it'd be ExampleModel(Base, pydantic.BaseModel) or similar). These are unfortunate but known limitations that'll be lifted in time.

The second thing is that -- ignore Pydantic entirely -- Python has some unintuitive behavior when it comes to whether annotations are required to be available at runtime or not, and this is where from __future__ import annotations comes into play.

If you omit from __future__ import annotations, then when you do:

from minimal_example_ruff_tch.field_model import F

class ExampleModel:
    field: F

Python actually does require that F is available at runtime. If you move from minimal_example_ruff_tch.field_model import F into an if TYPE_CHECKING: block, the code will error at runtime.

However, if you either use from __future__ import annotations or quote the annotation (like field: "F"), Ruff will correctly flag and move the import.

So, I think the known bug here is that we don't yet support multi-file analysis for these rules. In general, I don't know that I'd recommend using them with libraries like Pydantic that depend on the runtime evaluation of types. It's possible but not totally straightforward. Hopefully we'll improve there over time.

charliermarsh avatar Oct 09 '23 11:10 charliermarsh

I noticed a similar issue of Ruff having trouble evaluating base classes. It seems it doesn't reliably detect the bases – even within the same file.

given this example

from abc import ABC
from pydantic import BaseModel

class Model(BaseModel):
    x: str


class Empty(BaseModel):
    ...


class Abstract(Empty, ABC):
    inner: Model

Ruff evaluates the following classes and their bases (I simply placed some log statements in this function) https://github.com/astral-sh/ruff/blob/a1509dfc7c2b16f3762545fc802d49f5f03726e2/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs#L42

class Model  bases: [Some(["pydantic", "BaseModel"])]
class Abstract  bases: [None, Some(["abc", "ABC"])]

notice the absence of Empty and how Abstract then doesn't get detected as BaseModel

If you think it should be a separate issue, I'll gladly create a new one for this.

disrupted avatar Oct 09 '23 12:10 disrupted

@disrupted - Yeah this is separate -- we don't follow subclass chains within files. That one is solvable (not a one-line fix, but doesn't require any major changes). You're welcome to open another issue for it!

charliermarsh avatar Oct 09 '23 19:10 charliermarsh

Any updates on this? Ran into the same problem here.

Also, shouldn't runtime-evaluated-base-classes = ["example_model.ExampleModel"] tell ruff that any class references used for typing inside ExampleModel are required on runtime? I tried doing something along these lines but it didn't work.

BobVitorBob avatar Aug 23 '24 16:08 BobVitorBob

I ran into this issue today with a FastAPI route endpoint function with an Annotated parameter where pydantic will error if the annotated type is inside the TYPE_CHECKING block. It would be helpful if it was possible to define a set of imports that were excluded from the TC001-3 rules. Adding the import (as reported by the ruff error) to runtime-evaluated-base-classes did not make any difference.

Edit: Too quick there. I see exempt-modules also exists as a setting. Still, this is a little tricky to get to work.

vkbo avatar Aug 27 '25 07:08 vkbo

Are there any updates? Python 3.14 has been stable for several months now, and the restrictions of this rule prevent it from being used normally when there are several inherited base classes from pydantic that cannot be determined.

9en9i avatar Dec 12 '25 11:12 9en9i

There hasn't been an update to Ruff's multi-file analysis abilities. ty is able to analyze multiple files, and we hope to reuse that infrastructure in Ruff someday, but the single-file limitation is still in place.

It sounds like a couple of the comments here are reporting issues with single-file subclass chains. If that's the case, I'd be happy to take a look, as I believe that should work after #8768, but a minimal example, ideally in the playground, would be very helpful!

ntBre avatar Dec 12 '25 18:12 ntBre

@ntBre Thank you very much for your reply! Unfortunately, my problem is that the classes are located in different files. I look forward to this functionality becoming available.

9en9i avatar Dec 12 '25 20:12 9en9i