pylint
pylint copied to clipboard
Pylint incorrectly resolving type vs instances
Bug description
When trying to have a method that read from class variable that can be type or instance of that type pylint incorrectly.
First issue is in line 16 saying that data_schema_class is not callable (but it is). Second issue is that in line 35 it says that obj is not defined (because first paramer is self)
import typing
import marshmallow
class Base:
data_schema_class: typing.Optional[
typing.Union[typing.Type[marshmallow.Schema], marshmallow.Schema]
] = None # Marshmallow schema class for responses
def get_schema_instance(self) -> marshmallow.Schema:
if self.data_schema_class is None:
raise NotImplementedError()
if isinstance(self.data_schema_class, marshmallow.Schema):
return self.data_schema_class
schema = self.data_schema_class()
return schema
class DerivedOne(Base):
data_schema_class = marshmallow.Schema()
def test_me(self):
schema = self.get_schema_instance()
schema.dump({})
class DerivedTwo(Base):
data_schema_class = marshmallow.Schema
def tesst_me(self):
schema = self.get_schema_instance()
schema.dump({})
Configuration
No response
Command used
pylint --disable=C0114,C0115,C0116,R0903 pylint_error.py
Pylint output
(.venv) ➜ pylint pylint_error.py
************* Module pylint_error
pylint_error.py:16:17: E1102: self.data_schema_class is not callable (not-callable)
pylint_error.py:35:8: E1120: No value for argument 'obj' in unbound method call (no-value-for-parameter)
Expected behavior
It should pass the validation.
Pylint version
pylint 3.0.3
astroid 3.0.2
Python 3.11.2 (main, Jan 5 2024, 12:15:46) [Clang 15.0.0 (clang-1500.1.0.2.5)]
OS / Environment
MacOSX
Additional dependencies
marshmallow==3.20.1
I can reproduce that on Ubuntu 22.04, Python 3.10.12, pylint 3.1.0. I've stripped marshmallow from the example code, which leaves this:
# pylint: disable=missing-module-docstring
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=too-few-public-methods
import typing
class Schema:
def dump(self, obj):
pass
class Base:
data_schema_class: typing.Optional[
typing.Union[typing.Type[Schema], Schema]
] = None # Marshmallow schema class for responses
def get_schema_instance(self) -> Schema:
if self.data_schema_class is None:
raise NotImplementedError()
if isinstance(self.data_schema_class, Schema):
return self.data_schema_class
schema = self.data_schema_class() # line 26
return schema
class DerivedOne(Base):
data_schema_class = Schema()
def test_me(self):
schema = self.get_schema_instance()
schema.dump({})
class DerivedTwo(Base):
data_schema_class = Schema
def tesst_me(self):
schema = self.get_schema_instance()
schema.dump({}) # line 45
This produces the following output:
> pylint demo.py
************* Module demo
demo.py:26:17: E1102: self.data_schema_class is not callable (not-callable)
demo.py:45:8: E1120: No value for argument 'obj' in unbound method call (no-value-for-parameter)
------------------------------------------------------------------
Your code has been rated at 5.65/10 (previous run: 5.65/10, +0.00)
I've also tried latest commit (82ef6475) from the main branch:
> PYTHONPATH=. python -m pylint issue9541/demo.py
************* Module demo
issue9541/demo.py:15:23: R6003: Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well (consider-alternative-union-syntax)
issue9541/demo.py:16:8: R6003: Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well (consider-alternative-union-syntax)
issue9541/demo.py:26:17: E1102: self.data_schema_class is not callable (not-callable)
issue9541/demo.py:45:8: E1120: No value for argument 'obj' in unbound method call (no-value-for-parameter)
issue9541/demo.py:16:21: R6002: 'typing.Type' will be deprecated with PY39, consider using 'type' instead. Add 'from __future__ import annotations' as well (consider-using-alias)
------------------------------------------------------------------
Your code has been rated at 4.35/10 (previous run: 0.00/10, +4.35)
This emits additional R6003 and R6002 messages, maybe that can be used to create a workaround, @matejsp, while at the same time improving the future-compatibility of your code.
BTW: The documentation for typing of class objects (as opposed to instances) is https://docs.python.org/3/library/typing.html#the-type-of-class-objects. The example code there doesn't raise warnings about two calls that are made with faulty parameters (but a few others). Maybe fixing this less complex issue first will cause the more complex one to go away automagically. For the record, the code I was running pylint on is this:
from __future__ import annotations
class User: ...
class ProUser(User): ...
class TeamUser(User): ...
def make_new_user(user_class: type[User]) -> User:
return user_class()
make_new_user(User) # OK
make_new_user(ProUser) # Also OK: ``type[ProUser]`` is a subtype of ``type[User]``
make_new_user(TeamUser) # Still fine
make_new_user(User()) # Error: expected ``type[User]`` but got ``User``
make_new_user(int) # Error: ``type[int]`` is not a subtype of ``type[User]``