A decorator implementing the descriptor protocol is incorrectly regarded read-only
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 20.6k
- Forks
- 3.3k
- PR merge metrics
- PR metrics pending
Description
Bug Report
A class-based decorator that implements the data descriptor protocol is incorrectly regarded as read-only, and does not match against itself in a Protocol. This bug was discovered when trying to implement protocols that match against SQLAlchemy's declared_attr decorator.
To Reproduce
Pattern in typical use:
from sqlalchemy.orm import Mapped, declared_attr
class MyModel:
@declared_attr
@classmethod
def foo(cls) -> Mapped[int]: ...
reveal_type(foo) # declared_attr[int]
reveal_type(foo.__set__) # exists, so this is a data descriptor
reveal_type(foo.__get__) # Typed as `-> int | InstrumentedAttribute[int]`, unlike `property.__get__`
class MyProtocol:
foo: declared_attr[int]
o: MyProtocol = MyModel() # Error: MyProtocol.foo expected settable variable, got read-only attribute
Here is an isolated code sample without external dependencies. This bug does not appear when the method decorator is replaced with a function call after method definition, and appears to be a side-effect of how Mypy processes decorators.
https://mypy-play.net/?gist=5eb1e9262cd880a77fd3b9fb5bb7dca0
Marking the attribute as read-only in the Protocol using @property works when the attribute is accessed via the instance, but not when accessed via the class (as o.__class__.foo), because it gets typed as property.fget (Mypy) or as an error (Pyright), instead of the expected declared_attr.__get__ return type.
This problem exists in all versions of Mypy available at mypy-play, including the latest release 1.8.0. Pyright 1.1.338 does not have this problem.
Expected Behavior
A decorated method that is turned into a descriptor should not be regarded as read-only when matching against a protocol.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the isolated mypy-play sample and the decorator-processing and Protocol attribute-matching paths it exercises. Reproduce the read-only diagnostic, then trace how the decorated method's descriptor methods are typed; done means a data descriptor matches the Protocol as settable and class access preserves the expected get return type.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, sqlalchemy
- Domain
- compilers, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100