ruff
ruff copied to clipboard
F401 (unused import) false positive
Hello, I have an import that is used in typing and is getting removed by ruff (flake8 detects it as unused as well) here is the example:
from typing import Optional
import datetime # <--- gets removed by ruff
import pydantic
class SomeClass(pydantic.BaseModel):
datetime: Optional[datetime.datetime]
for now, i'm adding the comment # noqa: F401 to keep the import.
ruff version 0.0.195
Thank you for this project 🤩
Thank you! Will check this out.
Is this the exact snippet you're using? When I run Ruff on:
from typing import Optional
import datetime
import pydantic
class SomeClass(pydantic.BaseModel):
datetime: Optional[datetime.datetime]
It doesn't report any errors 🤔 Neither does flake8.
(I'm guessing that whatever the problem is, it may have to do with the use of datetime as the class property, which collides with the import name datetime.)
well, there are other classes definitions in the module, but none of them have the property datetime nor the type datetime the full list of imports is
from __future__ import annotations
import datetime # noqa: F401 (false positive)
from enum import Enum
from typing import List, Optional
import pydantic
could it be that the __future__ annotations module is interfering with ruff/flake8 ?
I'm using python 3.8 and I have set in ruff.toml target-version = "py38"
Oh, yeah, it could be __future__.
Here's an minimal reproducible example:
from __future__ import annotations
import datetime
from typing import Optional
class SomeClass:
datetime: Optional[datetime.datetime]
A little challenging to fix, won't happen immediately... My suggestion would be to use a different name for the class attribute, since these kinds of collisions can cause a lot of confusing behavior. For example, Mypy and Pyright handle this case differently, and I don't know which is correct:
from __future__ import annotations
import datetime
from typing import Optional
class SomeClass:
datetime: Optional[datetime.datetime]
def __init__(self, datetime: datetime.datetime):
self.datetime = datetime
SomeClass(datetime.datetime(...))
Mypy flags an error on line 9 (error: Name "datetime.datetime" is not defined), because datetime there is bound to the class property, not the import. But Pyright does not.
Is this more or less an extended version of the "A003 problem" where A003 is triggered in the following code because id apparently shadows a Python built-in?
class SomeClass:
id: int
Basically, datetime (or in my case uuid) is shadowing the import, so that the import becomes unusued. I wouldn't normally expect the second datetime in the field declaration datetime: Optional[datetime.datetime] to refer to the first one, would be a major gotcha for me, does this shadowing really happen?
An annotation like id: int or datetime: Optional[datetime.datetime] doesn’t bind anything at runtime, so it shouldn’t be considered as shadowing the outer binding.
An annotated assignment like id: int = 0 or datetime: Optional[datetime.datetime] = datetime.datetime.now(), however, does shadow from the perspective of some references:
class SomeClass:
# ↓ shadowed except under __future__.annotations
datetime: datetime.datetime = (
# ↓ not shadowed
datetime.datetime.now()
)
# ↓ shadowed except under __future__.annotations
other: datetime.datetime = (
# ↓ always shadowed
datetime
)
(The behavior for annotations can be observed at runtime with typing.get_type_hints(SomeClass).)
Mypy’s understanding of annotation shadowing seems to be quite busted. It accepts nonsense like
x = "oops"
class SomeClass:
x: int
print(x + 1)
which does str + int at runtime.
Came across this today - if it helps here is where the from __future__ import annotations issue was raised in pyflakes
- https://github.com/PyCQA/pyflakes/issues/648
- https://github.com/PyCQA/pyflakes/issues/713
- https://github.com/PyCQA/pyflakes/issues/773
I've spent a while looking into this. What we might need is this: https://github.com/microsoft/pyright/commit/27bf8c1f2.
I still have the issue @charliermarsh:
from __future__ import annotations
import datetime # <--- gets removed by ruff
import pydantic
class SomeClass(pydantic.BaseModel):
datetime: datetime.datetime
command: ruff --config ruff.toml --show-fixes --fix ./test.py
Fixed 2 errors:
- test.py:
1 × F401 (unused-import)
1 × I001 (unsorted-imports)
Ruff 0.0.269 Python 3.11.2
It hasn't been released. It'll be part of 0.0.270.
oh okay my bad, i'll wait for the next release then, thanks :)
Should be out soon, maybe today? We'll see! :)