Repository: streamlit/streamlit

Issue:
streamlit.experimental_memo causes errors with mypy
### Summary

mypy complains about @experimental_memo 

### Steps to reproduce

Code snippet (test.py):

```
import streamlit as st

@st.experimental_memo
def foo(value: int) -> None:
  st.write(value * 2)

foo("bar")
```

(this is the snippet from https://github.com/streamlit/streamlit/pull/4045)

Output from mypy test.py:

> test.py:3: error: No overload variant of "__call__" of "MemoAPI" matches argument type "Callable[[int], None]"
> test.py:3: note: Possible overload variants:
> test.py:3: note:     def __call__(func) -> MemoAPI
> test.py:3: note:     def __call__(persist, *, show_spinner: bool = ..., suppress_st_warning: bool = ..., max_entries: Optional[int] = ..., ttl: Optional[float] = ...) -> Callable[[F], F]
> Found 1 error in 1 file (checked 1 source file)

Output from mypy **--no-implicit-reexport** test.py (just because the code has a comment "pass mypy checking with implicit_reexport disabled"):

> test.py:3: error: Module has no attribute "experimental_memo"; maybe "experimental_show", "experimental_rerun", or "experimental_singleton"?

### Is this a regression?

According to https://github.com/streamlit/streamlit/pull/4045 it is.

### Debug info

- Streamlit, version 1.8.1
- Python 3.10.4
- mypy 0.942
- Microsoft Windows [Version 10.0.19042.1586]

streamlit.experimental_memo causes errors with mypy
### Summary

mypy complains about @experimental_memo 

### Steps to reproduce

Code snippet (test.py):

```
import streamlit as st

@st.experimental_memo
def foo(value: int) -> None:
  st.write(value * 2)

foo("bar")
```

(this is the snippet from https://github.com/streamlit/streamlit/pull/4045)

Output from mypy test.py:

> test.py:3: error: No overload variant of "__call__" of "MemoAPI" matches argument type "Callable[[int], None]"
> test.py:3: note: Possible overload variants:
> test.py:3: note:     def __call__(func) -> MemoAPI
> test.py:3: note:     def __call__(persist, *, show_spinner: bool = ..., suppress_st_warning: bool = ..., max_entries: Optional[int] = ..., ttl: Optional[float] = ...) -> Callable[[F], F]
> Found 1 error in 1 file (checked 1 source file)

Output from mypy **--no-implicit-reexport** test.py (just because the code has a comment "pass mypy checking with implicit_reexport disabled"):

> test.py:3: error: Module has no attribute "experimental_memo"; maybe "experimental_show", "experimental_rerun", or "experimental_singleton"?

### Is this a regression?

According to https://github.com/streamlit/streamlit/pull/4045 it is.

### Debug info

- Streamlit, version 1.8.1
- Python 3.10.4
- mypy 0.942
- Microsoft Windows [Version 10.0.19042.1586]



Discussion:
Thanks for reporting this @clo-vis.

It looks like this was caused by a combination of a bug in mypy (https://github.com/python/mypy/issues/7781) as well as some refactoring that was done to wrap the `st.experimental_memo` API in a class.

This one might be a bit tricky for us to resolve in the near future since `st.experimental_memo` and `st.experimental_singleton` were wrapped in classes so that we could add support for the `st.experimental_{memo,singleton}.clear()` APIs, and it'd now be a worse crime to break those APIs to fix this issue with `mypy`. The `mypy` bug also seems to have existed for the past few years 😕 

(CC @AnOctopus and @tconkling for visibility)
Thanks for reporting this @clo-vis.

It looks like this was caused by a combination of a bug in mypy (https://github.com/python/mypy/issues/7781) as well as some refactoring that was done to wrap the `st.experimental_memo` API in a class.

This one might be a bit tricky for us to resolve in the near future since `st.experimental_memo` and `st.experimental_singleton` were wrapped in classes so that we could add support for the `st.experimental_{memo,singleton}.clear()` APIs, and it'd now be a worse crime to break those APIs to fix this issue with `mypy`. The `mypy` bug also seems to have existed for the past few years 😕 

(CC @AnOctopus and @tconkling for visibility)

Resolving patch:
diff --git a/lib/streamlit/caching/memo_decorator.py b/lib/streamlit/caching/memo_decorator.py
--- a/lib/streamlit/caching/memo_decorator.py
+++ b/lib/streamlit/caching/memo_decorator.py
@@ -198,14 +198,13 @@ class MemoAPI:
 
     # Bare decorator usage
     @overload
-    @staticmethod
-    def __call__(func: F) -> F:
+    def __call__(self, func: F) -> F:
         ...
 
     # Decorator with arguments
     @overload
-    @staticmethod
     def __call__(
+        self,
         *,
         persist: Optional[str] = None,
         show_spinner: bool = True,
@@ -215,8 +214,11 @@ def __call__(
     ) -> Callable[[F], F]:
         ...
 
-    @staticmethod
+    # __call__ should be a static method, but there's a mypy bug that
+    # breaks type checking for overloaded static functions:
+    # https://github.com/python/mypy/issues/7781
     def __call__(
+        self,
         func: Optional[F] = None,
         *,
         persist: Optional[str] = None,


Test patch: