Repository: python/mypy

Issue:
Decorated property not supported
``` python
class Foo(object):
    @property
    @some_decorator
    def foo(self):
        ...
```

is valid in Python 2, but MyPy complains `Decorated property not supported`.
Adding `type: ignore` does not ignore the error.



Discussion:
Good news: if you put the `# type: ignore` on the `@property` line it's silent.

A bit off topic, but I came across this warning today and it made me wonder about something. I have a BLE Connection class, and each instance has the attributes characteristics and services, which are implemented with the @property decorator so that it can actually fetch them a new every time without assigning a lambda or making them a method. Fetching can raise an exception though, and I'm wondering how normal is it that something like "a = b.attribute" can raise an exception that can't be just avoided by rearranging the code like from a NameError or a SyntaxError?
Since properties are common in Python, that's not something to be concerned about.
Thanks for the clarification.
> Good news: if you put the `# type: ignore` on the `@property` line it's silent.

Note it appears to need to be on the outer decorator, so if you have the opposite order to OP:
```
@some_decorator
@property
def foo
```
then it should be on the `@some_decorator` line.
Correct, messages related to a function (or, sometimes, its arguments)
always appear on the line of the first decorator, if there is one,
otherwise on the linke containing 'def'. (I consider this a slight flaw, as
occasionally it makes it hard to find the root cause for an error message.)

The rule "put the '# type: ignore' on the line where the error appears" is
pretty simple to remember though.

Hello

I have the same issue. In my project I use additional decorators for properties very often. Thus mypy sends me hundreds of error with "error: Decorated property not supported".

First of all, I don't understand the error message. Does it mean mypy does not support decorated properties, or is decorating a property related to any code quality issue in general? Because if just mypy is not supporting decorated properties, but the pattern in general is not considered as bad practice, I don't know, why mypy is giving me an error here. It should be at maximum a warning right?

Second, if it is just a mypy issue, I really want to ignore it. Is there any way to ignore exactly this error on a global level? At the moment, I'm only aware of ignoring type errors for a single line, but since this pattern is extremely common in my code, I don't want to add the "ignore line" comment on hundreds of lines in my code.

Thanks in advance for any suggestions and assistance.
Mypy cannot properly type check code involving decorated properties. Currently there's no way to ignore all such errors, but this is something mypy may support in the future. 

As a workaround, you could try defining your own alias to the `property` decorator that is annotated to return `Any`. If you use your custom property decorator (that at runtime behaves exactly like `property`) for all decorated properties, mypy will silently give all such properties the `Any` type.
Same issue here. I have to use decorated property because the `abc` module have deprecated `@abstractproperty`. The only way to define an abstract property is to use decorated property:
```
class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

    @my_abstract_property.setter
    @abstractmethod
    def my_abstract_property(self, val):
        ...
```
If generally decorated properties cannot be supported, can mypy special-case and support `@abstractmethod`?
> If generally decorated properties cannot be supported, can mypy special-case and support `@abstractmethod`?

Yes, this (supporting abstract settable properties) is actually what is done by PR https://github.com/python/mypy/pull/8111 (it is a few line fix I added because this broke couple tests for the main PR matter).
> Same issue here. I have to use decorated property because the `abc` module have deprecated `@abstractproperty`. The only way to define an abstract property is to use decorated property:
> 
> ```
> class C(ABC):
>     @property
>     @abstractmethod
>     def my_abstract_property(self):
>         ...
> 
>     @my_abstract_property.setter
>     @abstractmethod
>     def my_abstract_property(self, val):
>         ...
> ```
> 
> If generally decorated properties cannot be supported, can mypy special-case and support `@abstractmethod`?

This is exactly my use case too.
Wish there was some easy way to mark the decorator function to tell mypy to ignore it or a way to tell mypy to always ignore such decorators.

It is really ugly to have to slap a `# type: ignore` comment on every use of a property decorator.

Let's put it this way: what is the concrete benefit of the existing behavior?

Same here :)
What's the progress on this issue?

I'm using the `@functools.lru_cache` decorator on many `@property` methods and this "warning" shows up on them all.

```python
from functools import lru_cache

class MyClass:
    def __init__(self, year: int):
        self.current_year = 2020
        self.year = year

    @property  # Decorated property not supported - mypy(error)
    @lru_cache
    def how_long_ago(self) -> int:
        return self.current_year - self.year
```

I'm using the "workaround" of adding `# type: ignore`, but it's far from ideal.

```python
from functools import lru_cache

class MyClass:
    def __init__(self, year: int):
        self.current_year = 2020
        self.year = year

    @property  # type: ignore
    @lru_cache
    def how_long_ago(self) -> int:
        return self.current_year - self.year
```

`functools` should have a [`@cached_property`](https://docs.python.org/dev/library/functools.html?highlight=s#functools.cached_property) decorator that would probably not trigger this "warning" from `mypy`, but it's only for Python ≥ `3.8`, and I'm currently stuck on Python ≤ `3.7`, so I can't use it.

```python
from functools import cached_property

class MyClass:
    def __init__(self, year: int):
        self.current_year = 2020
        self.year = year

    @cached_property  # Python >= 3.8
    def how_long_ago(self) -> int:
        return self.current_year - self.year
```

## Update

For my specific use case, I believe the following could do the trick:

```python
try:
    from functools import cached_property
except ImportError:
    from functools import lru_cache

    def cached_property(func):  # type: ignore
        return property(lru_cache(func))

class MyClass:
    def __init__(self, year: int):
        self.current_year = 2020
        self.year = year

    @cached_property
    def how_long_ago(self) -> int:
        return self.current_year - self.year
```

And I believe this pattern of combining common decorators can also apply to other use cases.
<https://stackoverflow.com/questions/5409450/can-i-combine-two-decorators-into-a-single-one-in-python>

> As a workaround, you could try defining your own alias to the `property` decorator that is annotated to return `Any`. If you use your custom property decorator (that at runtime behaves exactly like `property`) for all decorated properties, mypy will silently give all such properties the `Any` type.

For others' reference, this can be done like so:
```
def property(fn) -> Any:
    """Resolves mypy 'error: Decorated property not supported'"""
    return property(fn)
```
If the system is incapable of detecting whether a decorated property works, why not just stop emitting the error, or at the very least give an option to disable this error that isn't a workaround like having to type `#type: ignore` on every line with a decorated property (which is ugly)? There seems to be no benefit to this error, except to say "don't trust mypy with decorated properties", which to me (and it seems others) is not very beneficial. Instead of emitting an error I would think it would make more sense to just note that this is a caveat to the program.
Is there absolutely nobody reading this who would like to contribute a fix to mypy? It’s open source! Please, we can use more contributors.
@18goldr You can have a look at the pull request (#8108), which was declined one year ago. It was addressing the issue that it is not possible to ignore this specific error and it introduced a regex system of ignoring error messages, which helps me a lot of making mypy silent when it comes to properties. 

Unfortunately it was declined and the maintainers are not aiming for any alternative solution here, but you can fork mypy and apply the pull-request to your fork. 
I'm having an issue but I'm not entirely sure it is the same as being discussed here. With the following snippet:
```python
	def archive_members(
		*, archive: tarfile.TarFile, root_path: str, search_path: str = None
	):
		l = len(f'{root_path}/')
		for member in archive.getmembers():
			if member.path.startswith(f'{root_path}/{search_path or ""}'):
				member.path = member.path[l:]
				yield member
```

mypy complains on use of `member.path` with error: `"TarInfo" has no attribute "path"`.

Now, `path` is correctly not an attribute but a decorated property method, however, I'm not getting the error mentioned here which says `Decorator not supported`, but `no attribute`. Does this fall under the same category?
No, it's not related. You should ask questions on Gitter. It looks like the typeshed stubs for tarfile could use some improvement (although you could instead use `name`):

<details>

```
$ stubtest tarfile --concise
tarfile.TarFile.OPEN_METH is not present in stub
tarfile.TarFile.bz2open is not present in stub
tarfile.TarFile.errors variable differs from runtime type None
tarfile.TarFile.fileobject is not present in stub
tarfile.TarFile.gzopen is not present in stub
tarfile.TarFile.taropen is not present in stub
tarfile.TarFile.xzopen is not present in stub
tarfile.TarInfo.chksum is not present in stub
tarfile.TarInfo.create_gnu_header is not present in stub
tarfile.TarInfo.create_pax_global_header is not present in stub
tarfile.TarInfo.create_pax_header is not present in stub
tarfile.TarInfo.create_ustar_header is not present in stub
tarfile.TarInfo.devmajor is not present in stub
tarfile.TarInfo.devminor is not present in stub
tarfile.TarInfo.get_info is not present in stub
tarfile.TarInfo.issparse is not present in stub
tarfile.TarInfo.linkpath is not present in stub
tarfile.TarInfo.offset is not present in stub
tarfile.TarInfo.offset_data is not present in stub
tarfile.TarInfo.path is not present in stub
tarfile.TarInfo.sparse is not present in stub
tarfile.TarInfo.tarfile is not present in stub
```
</details>
> Is there absolutely nobody reading this who would like to contribute a fix to mypy? It’s open source! Please, we can use more contributors.

@gvanrossum I would, if there is a clear task description or expected behavior of what we hope to accomplish. 
@andrewnaguib we'd want code like that in the top post to not produce errors, and similar incorrect code (e.g., a decorator that produces a callable with too many arguments) should produce an error.
I wondered the same few hours ago but I was lucky that I was able to just rewrite my code to avoid use of lru_cache in my case but I still have no idea how to fix it if I really need both.
@JelleZijlstra Would ensuring that the latest decorator (in case there are sevaral ones) returns the same type be enough?
@andrewnaguib not necessarily the same type. Consider the following:

```python
T = TypeVar("T")
U = TypeVar("U")

decorator: Callable[[Callable[[T, int], U]], Callable[[T], U]]

class X:
     @property
     @decorator
     def prop(self, x: int) -> str:
           pass
```
This code is correct, because the decorated `prop` method matches what `property` wants: the decorator removes the `int` argument.
Seems like a simple fix would be to simply add top-level option to turn off this warning or just eliminate it entirely. What is the purpose of this warning in any case? 

It just tells us that mypy isn't smart enough to handle this code.
> Is there absolutely nobody reading this who would like to contribute a fix to mypy? It’s open source! Please, we can use more contributors.

One way to make it easier would be for someone from experienced contributors to outline high level design of how it could be done, or some areas in the code to get started. Or maybe a sketch of unit test reproducing this error in terms of internals of mypy.
We don't need a fancy proposal for this. Just turn off the warning or make it an actual warning rather than an error we cannot ignore without polluting our code with pragmas. What use this warning as it stands now?

Mypy should simply ignore decorators on properties for now.


I am going to try adding support for this feature. Going to send a PR in the next couple of days.

I hope it will work! 🙂 
@sobolevn Let me know if you need help, I was confused on this warning as well.

I have code that once used  `abstractmethod` from `functools` and `mypy` never complained about that. But, when aliasing this `abstractmethod`, it suddenly does complain.
This is pretty much a useless error. It doesn't tell you anything about the code. It just tells you that mypy doesn't implement a feature. Why should we have to load our code up with `# type: ignore` comments for this?

Just remove this error and call it a day.

it looks like mypy does actually support properties on decorated functions, it just claims not to:

https://mypy-play.net/?mypy=latest&python=3.10&gist=614fc752589b60b66011de44e02d9524&flags=strict

```python
from collections.abc import Callable
from typing import ParamSpec, TypeVar

P = ParamSpec("P")
T = TypeVar("T")

import functools

def some_decorator(f: Callable[P, int]) -> Callable[P, str]:
    @functools.wraps(f)
    def decorator(*args: P.args, **kwargs: P.kwargs) -> str:
        return f"{f(*args, **kwargs)}"
        
    return decorator
    
    
def some_decorator2(p: property) -> property:
    return p

class Foo:
    
    # this is a propertied decorator and fairly normal
    @property  # type: ignore[misc]
    @some_decorator
    def foo(self) -> int:
        pass
    

    # this is a decorated property and an unusual construct 
    @some_decorator2
    @property
    def bar(self) -> int:
        pass
    
    
f = Foo()
reveal_type(f.foo)
```

so there's actually two issues here. "mypy doesn't support decorated properties" and "mypy claims to not support propertied decorators when it actually does"

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -4310,6 +4310,9 @@ def visit_decorator(self, e: Decorator) -> None:
         e.var.type = sig
         e.var.is_ready = True
         if e.func.is_property:
+            if isinstance(sig, CallableType):
+                if len([k for k in sig.arg_kinds if k.is_required()]) > 1:
+                    self.msg.fail("Too many arguments for property", e)
             self.check_incompatible_property_override(e)
         if e.func.info and not e.func.is_dynamic():
             self.check_method_override(e)
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1049,6 +1049,8 @@ def analyze_overload_sigs_and_impl(
                 else:
                     item.func.is_overload = True
                     types.append(callable)
+                    if item.var.is_property:
+                        self.fail("An overload can not be a property", item)
             elif isinstance(item, FuncDef):
                 if i == len(defn.items) - 1 and not self.is_stub_file:
                     impl = item
@@ -1168,7 +1170,7 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
         deleted_items = []
         for i, item in enumerate(items[1:]):
             if isinstance(item, Decorator):
-                if len(item.decorators) == 1:
+                if len(item.decorators) >= 1:
                     node = item.decorators[0]
                     if isinstance(node, MemberExpr):
                         if node.name == "setter":
@@ -1176,8 +1178,10 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
                             first_item.var.is_settable_property = True
                             # Get abstractness from the original definition.
                             item.func.abstract_status = first_item.func.abstract_status
-                else:
-                    self.fail("Decorated property not supported", item)
+                    else:
+                        self.fail(
+                            f"Only supported top decorator is @{first_item.func.name}.setter", item
+                        )
                 item.func.accept(self)
             else:
                 self.fail(f'Unexpected definition for property "{first_item.func.name}"', item)
@@ -1258,6 +1262,7 @@ def visit_decorator(self, dec: Decorator) -> None:
             d.accept(self)
         removed: List[int] = []
         no_type_check = False
+        could_be_decorated_property = False
         for i, d in enumerate(dec.decorators):
             # A bunch of decorators are special cased here.
             if refers_to_fullname(d, "abc.abstractmethod"):
@@ -1288,8 +1293,6 @@ def visit_decorator(self, dec: Decorator) -> None:
                 elif refers_to_fullname(d, "functools.cached_property"):
                     dec.var.is_settable_property = True
                 self.check_decorated_function_is_method("property", dec)
-                if len(dec.func.arguments) > 1:
-                    self.fail("Too many arguments", dec.func)
             elif refers_to_fullname(d, "typing.no_type_check"):
                 dec.var.type = AnyType(TypeOfAny.special_form)
                 no_type_check = True
@@ -1304,6 +1307,10 @@ def visit_decorator(self, dec: Decorator) -> None:
                     removed.append(i)
                 else:
                     self.fail("@final cannot be used with non-method functions", d)
+            elif not dec.var.is_property:
+                # We have seen a "non-trivial" decorator before seeing @property, if
+                # we will see a @property later, give an error, as we don't support this.
+                could_be_decorated_property = True
         for i in reversed(removed):
             del dec.decorators[i]
         if (not dec.is_overload or dec.var.is_property) and self.type:
@@ -1311,8 +1318,10 @@ def visit_decorator(self, dec: Decorator) -> None:
             dec.var.is_initialized_in_class = True
         if not no_type_check and self.recurse_into_functions:
             dec.func.accept(self)
-        if dec.decorators and dec.var.is_property:
-            self.fail("Decorated property not supported", dec)
+        if could_be_decorated_property and dec.decorators and dec.var.is_property:
+            self.fail("Decorators on top of @property are not supported", dec)
+        if (dec.func.is_static or dec.func.is_class) and dec.var.is_property:
+            self.fail("Only instance methods can be decorated with @property", dec)
         if dec.func.abstract_status == IS_ABSTRACT and dec.func.is_final:
             self.fail(f"Method {dec.func.name} is both abstract and final", dec)
 


Test patch:
diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test
--- a/test-data/unit/check-abstract.test
+++ b/test-data/unit/check-abstract.test
@@ -1030,3 +1030,23 @@ def deco(cls: Type[T]) -> Type[T]: ...
 class A(metaclass=ABCMeta):
     @abstractmethod
     def foo(self, x: int) -> None: ...
+
+[case testAbstractPropertiesAllowed]
+from abc import abstractmethod
+
+class B:
+    @property
+    @abstractmethod
+    def x(self) -> int: ...
+    @property
+    @abstractmethod
+    def y(self) -> int: ...
+    @y.setter
+    @abstractmethod
+    def y(self, value: int) -> None: ...
+
+B()  # E: Cannot instantiate abstract class "B" with abstract attributes "x" and "y"
+b: B
+b.x = 1  # E: Property "x" defined in "B" is read-only
+b.y = 1
+[builtins fixtures/property.pyi]
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -7006,8 +7006,8 @@ class A:
     def y(self) -> int: ...
     @y.setter
     def y(self, value: int) -> None: ...
-    @dec
-    def y(self) -> None: ...  # TODO: This should generate an error
+    @dec  # E: Only supported top decorator is @y.setter
+    def y(self) -> None: ...
 
 reveal_type(A().y)  # N: Revealed type is "builtins.int"
 [builtins fixtures/property.pyi]
@@ -7044,7 +7044,7 @@ reveal_type(D1() + 0.5)  # N: Revealed type is "__main__.D1"
 [builtins fixtures/primitives.pyi]
 
 [case testRefMethodWithDecorator]
-from typing import Type
+from typing import Type, final
 
 class A:
     pass
@@ -7058,7 +7058,7 @@ class B:
         return A
 
 class C:
-    @property
+    @final
     @staticmethod
     def A() -> Type[A]:
         return A
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
--- a/test-data/unit/check-functions.test
+++ b/test-data/unit/check-functions.test
@@ -2589,3 +2589,88 @@ def a(b: any): pass # E: Function "builtins.any" is not valid as a type \
 def a(b: callable): pass # E: Function "builtins.callable" is not valid as a type \
                          # N: Perhaps you meant "typing.Callable" instead of "callable"?
 [builtins fixtures/callable.pyi]
+
+[case testDecoratedProperty]
+from typing import TypeVar, Callable, final
+
+T = TypeVar("T")
+
+def dec(f: Callable[[T], int]) -> Callable[[T], str]: ...
+def dec2(f: T) -> T: ...
+
+class A:
+    @property
+    @dec
+    def f(self) -> int: pass
+    @property
+    @dec2
+    def g(self) -> int: pass
+reveal_type(A().f)  # N: Revealed type is "builtins.str"
+reveal_type(A().g)  # N: Revealed type is "builtins.int"
+
+class B:
+    @final
+    @property
+    @dec
+    def f(self) -> int: pass
+reveal_type(B().f)  # N: Revealed type is "builtins.str"
+
+class C:
+    @property  # E: Only instance methods can be decorated with @property
+    @classmethod
+    def f(cls) -> int: pass
+reveal_type(C().f)  # N: Revealed type is "builtins.int"
+[builtins fixtures/property.pyi]
+[out]
+
+[case testDecoratedPropertySetter]
+from typing import TypeVar, Callable, final
+
+T = TypeVar("T")
+def dec(f: T) -> T: ...
+
+class A:
+    @property
+    @dec
+    def f(self) -> int: pass
+    @f.setter
+    @dec
+    def f(self, v: int) -> None: pass
+reveal_type(A().f)  # N: Revealed type is "builtins.int"
+
+class B:
+    @property
+    @dec
+    def f(self) -> int: pass
+    @dec # E: Only supported top decorator is @f.setter
+    @f.setter
+    def f(self, v: int) -> None: pass
+
+class C:
+    @dec  # E: Decorators on top of @property are not supported
+    @property
+    def f(self) -> int: pass
+    @f.setter
+    @dec
+    def f(self, v: int) -> None: pass
+[builtins fixtures/property.pyi]
+[out]
+
+[case testInvalidArgCountForProperty]
+from typing import Callable, TypeVar
+
+T = TypeVar("T")
+def dec(f: Callable[[T], int]) -> Callable[[T, int], int]: ...
+
+class A:
+    @property  # E: Too many arguments for property
+    def f(self, x) -> int: pass
+    @property  # E: Too many arguments for property
+    @dec
+    def e(self) -> int: pass
+    @property
+    def g() -> int: pass   # E: Method must have at least one argument
+    @property
+    def h(self, *args, **kwargs) -> int: pass   # OK
+[builtins fixtures/property.pyi]
+[out]
diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test
--- a/test-data/unit/check-functools.test
+++ b/test-data/unit/check-functools.test
@@ -119,8 +119,8 @@ class Child(Parent):
     def f(self) -> str: pass
     @cached_property
     def g(self) -> int: pass
-    @cached_property
-    def h(self, arg) -> int: pass  # E: Too many arguments
+    @cached_property  # E: Too many arguments for property
+    def h(self, arg) -> int: pass
 reveal_type(Parent().f)  # N: Revealed type is "builtins.str"
 reveal_type(Child().f)  # N: Revealed type is "builtins.str"
 reveal_type(Child().g)  # N: Revealed type is "builtins.int"
diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test
--- a/test-data/unit/semanal-errors.test
+++ b/test-data/unit/semanal-errors.test
@@ -1205,23 +1205,13 @@ def f() -> int: pass
 [builtins fixtures/property.pyi]
 [out]
 
-[case testInvalidArgCountForProperty]
-import typing
-class A:
-    @property
-    def f(self, x) -> int: pass  # E: Too many arguments
-    @property
-    def g() -> int: pass   # E: Method must have at least one argument
-[builtins fixtures/property.pyi]
-[out]
-
 [case testOverloadedProperty]
 from typing import overload
 class A:
-    @overload  # E: Decorated property not supported
+    @overload  # E: Decorators on top of @property are not supported
     @property
     def f(self) -> int: pass
-    @property  # E: Decorated property not supported
+    @property  # E: Only supported top decorator is @f.setter
     @overload
     def f(self) -> int: pass
 [builtins fixtures/property.pyi]
@@ -1232,7 +1222,7 @@ from typing import overload
 class A:
     @overload  # E: An overloaded function outside a stub file must have an implementation
     def f(self) -> int: pass
-    @property  # E: Decorated property not supported
+    @property  # E: An overload can not be a property
     @overload
     def f(self) -> int: pass
 [builtins fixtures/property.pyi]
@@ -1242,10 +1232,10 @@ class A:
 import typing
 def dec(f): pass
 class A:
-    @dec  # E: Decorated property not supported
+    @dec  # E: Decorators on top of @property are not supported
     @property
     def f(self) -> int: pass
-    @property  # E: Decorated property not supported
+    @property  # OK
     @dec
     def g(self) -> int: pass
 [builtins fixtures/property.pyi]