pylint
pylint copied to clipboard
`not-callable` false positive for class
Bug description
Pylint complains about func.myfunc
in the snippet below not being callable.
# pylint: disable=invalid-name, missing-class-docstring, missing-function-docstring, missing-module-docstring, too-few-public-methods
from typing import TYPE_CHECKING, Any, Type, TypeVar
_T = TypeVar("_T", bound=Any)
class myfunc:
def __init__(self, *args: Any) -> None:
pass
class _FunctionGenerator:
if TYPE_CHECKING:
@property
def myfunc(self) -> Type[myfunc]:
...
func = _FunctionGenerator()
func.myfunc(1, 2, 3)
A concrete use case is in https://github.com/sqlalchemy/sqlalchemy/issues/9189
Configuration
No response
Command used
pylint test4b.py
Pylint output
************* Module test4b
test4b.py:19:0: E1102: func.myfunc is not callable (not-callable)
Expected behavior
Pylint should not complain about func.myfunc not being callable
Pylint version
pylint 2.16.0b1
astroid 2.13.3
Python 3.10.4 (main, Dec 5 2022, 21:19:56) [GCC 9.4.0]
OS / Environment
Ubuntu
Additional dependencies
No response
Thanks @emontnemery for the report. I can reproduce this using the example over in the sqlalchemy issue (copy/paste below). The example in the description doesn't run successfully as a Python script however.
from sqlalchemy import func, select, Integer
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.sql.selectable import Select
class Base(DeclarativeBase):
pass
class MyTable(Base):
__tablename__ = "host_entry"
id = mapped_column(Integer, primary_key=True)
def state_attrs_exist(attr: int | None) -> Select:
"""Check if a state attributes id exists in the states table."""
return select(func.min(MyTable.id)).where(MyTable.id == attr)
The example in the description doesn't run successfully as a Python script however.
Sorry, I did not know that was a requirement. Should I edit the example?
The example in the description doesn't run successfully as a Python script however.
Sorry, I did not know that was a requirement. Should I edit the example?
No worries! Its handy for us if the example is working code so that we can focus on only the reported false positive. If you have time to edit it then please feel free but otherwise the other example should be sufficient I think. I mentioned it originally just as a heads-up and to clarify to any future readers.
Here is a working script. The last line produces an error message in pylint, but only if the sqlalchemy version is at least 2.0.
C:\temp\sa_func_count.py:19:31: E1102: sa.func.count is not callable (not-callable)
import sqlalchemy as sa
metadata = sa.MetaData()
user = sa.Table(
"user",
metadata,
sa.Column("user_id", sa.Integer, primary_key=True),
sa.Column("user_name", sa.String(16), nullable=False),
sa.Column("email_address", sa.String(60)),
sa.Column("nickname", sa.String(50), nullable=False),
)
db_uri = "sqlite:///:memory:"
engine = sa.create_engine(db_uri)
with engine.connect() as conn:
user.create(bind=conn)
assert (
conn.execute(sa.select(sa.func.count()).select_from(user)).fetchall()[0][0] == 0
)
+1
bump
In the if TYPE_CHECKING
block, the myfunc
property is set to None
, which is not callable. So the underlying issue is that pylint is using the if TYPE_CHECKING
block to infer the type, but it is not using the type hint provided there (see discussion of type hint vs. inference here: https://github.com/pylint-dev/pylint/issues/4813). Type inference comes from the astroid package, and I don't see a way to make it ignore type checking blocks.
Here is a modification of the original script which runs in python, but outputs the not-callable
false positive:
# pylint: disable=invalid-name, missing-class-docstring, missing-function-docstring, missing-module-docstring, too-few-public-methods
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Type
class myfunc:
def __init__(self, *args: Any) -> None:
pass
class _FunctionGenerator:
def __getattr__(self, name: str) -> _FunctionGenerator:
return _FunctionGenerator()
def __call__(self, *args: Any) -> None:
pass
if TYPE_CHECKING:
@property
def myfunc(self) -> Type[myfunc]: ...
func = _FunctionGenerator()
func.myfunc(1, 2, 3)
If you modify the property definition to return something that is callable, then pylint does not output not-callable:
# pylint: disable=invalid-name, missing-class-docstring, missing-function-docstring, missing-module-docstring, too-few-public-methods
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Type
class myfunc:
def __init__(self, *args: Any) -> None:
pass
class _FunctionGenerator:
def __getattr__(self, name: str) -> _FunctionGenerator:
return _FunctionGenerator()
def __call__(self, *args: Any) -> None:
pass
if TYPE_CHECKING:
@property
def myfunc(self) -> Type[myfunc]:
return myfunc
func = _FunctionGenerator()
func.myfunc(1, 2, 3)
Removing the if TYPE_CHECKING
block also makes the not-callable output go away.