Parameterized cache decorator metrics names
diff --git a/lib/streamlit/runtime/caching/__init__.py b/lib/streamlit/runtime/caching/__init__.py
index 1d37ec2aa582..f72f730ef9c8 100644
--- a/lib/streamlit/runtime/caching/__init__.py
+++ b/lib/streamlit/runtime/caching/__init__.py
@@ -106,18 +106,18 @@ def suppress_cached_st_function_warning() -> Iterator[None]:
 )
 
 # Create and export public API singletons.
-cache_data = CacheDataAPI()
-cache_resource = CacheResourceAPI()
+cache_data = CacheDataAPI(decorator_metric_name="cache_data")
+cache_resource = CacheResourceAPI(decorator_metric_name="cache_resource")
 
 # Deprecated singletons
 experimental_memo = deprecate_obj_name(
-    cache_data,
+    CacheDataAPI(decorator_metric_name="experimental_memo"),
     old_name="experimental_memo",
     new_name="cache_data",
     removal_date="2023.04.01",
 )
 experimental_singleton = deprecate_obj_name(
-    cache_resource,
+    CacheResourceAPI(decorator_metric_name="experimental_singleton"),
     old_name="experimental_singleton",
     new_name="cache_resource",
     removal_date="2023.04.01",
diff --git a/lib/streamlit/runtime/caching/cache_data_api.py b/lib/streamlit/runtime/caching/cache_data_api.py
index b1baa0ab614b..a4d12f6a2d69 100644
--- a/lib/streamlit/runtime/caching/cache_data_api.py
+++ b/lib/streamlit/runtime/caching/cache_data_api.py
@@ -211,6 +211,21 @@ class CacheDataAPI:
     st.cache_data.clear().
     """
 
+    def __init__(self, decorator_metric_name: str):
+        """Create a CacheDataAPI instance.
+
+        Parameters
+        ----------
+        decorator_metric_name
+            The metric name to record for decorator usage. `@st.experimental_memo` is
+            deprecated, but we're still supporting it and tracking its usage separately
+            from `@st.cache_data`.
+        """
+
+        # Parameterize the decorator metric name.
+        # (Ignore spurious mypy complaints - https://github.com/python/mypy/issues/2427)
+        self._decorator = gather_metrics(decorator_metric_name, self._decorator)  # type: ignore
+
     # Type-annotate the decorator function.
     # (See https://mypy.readthedocs.io/en/stable/generics.html#decorator-factories)
     F = TypeVar("F", bound=Callable[..., Any])
@@ -234,10 +249,6 @@ def __call__(
     ) -> Callable[[F], F]:
         ...
 
-    # __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
-    @gather_metrics("cache_data")
     def __call__(
         self,
         func: F | None = None,
@@ -248,6 +259,27 @@ def __call__(
         max_entries: int | None = None,
         ttl: float | timedelta | None = None,
         experimental_allow_widgets: bool = False,
+    ):
+        return self._decorator(
+            func,
+            persist=persist,
+            show_spinner=show_spinner,
+            suppress_st_warning=suppress_st_warning,
+            max_entries=max_entries,
+            ttl=ttl,
+            experimental_allow_widgets=experimental_allow_widgets,
+        )
+
+    @staticmethod
+    def _decorator(
+        func: F | None = None,
+        *,
+        persist: str | None = None,
+        show_spinner: bool | str = True,
+        suppress_st_warning: bool = False,
+        max_entries: int | None = None,
+        ttl: float | timedelta | None = None,
+        experimental_allow_widgets: bool = False,
     ):
         """Function decorator to cache function executions.
 
diff --git a/lib/streamlit/runtime/caching/cache_resource_api.py b/lib/streamlit/runtime/caching/cache_resource_api.py
index f3f5c4baba9b..95f5e5e54ca2 100644
--- a/lib/streamlit/runtime/caching/cache_resource_api.py
+++ b/lib/streamlit/runtime/caching/cache_resource_api.py
@@ -136,6 +136,21 @@ class CacheResourceAPI:
     and st.cache_resource.clear().
     """
 
+    def __init__(self, decorator_metric_name: str):
+        """Create a CacheResourceAPI instance.
+
+        Parameters
+        ----------
+        decorator_metric_name
+            The metric name to record for decorator usage. `@st.experimental_singleton` is
+            deprecated, but we're still supporting it and tracking its usage separately
+            from `@st.cache_resource`.
+        """
+
+        # Parameterize the decorator metric name.
+        # (Ignore spurious mypy complaints - https://github.com/python/mypy/issues/2427)
+        self._decorator = gather_metrics(decorator_metric_name, self._decorator)  # type: ignore
+
     # Type-annotate the decorator function.
     # (See https://mypy.readthedocs.io/en/stable/generics.html#decorator-factories)
 
@@ -157,10 +172,6 @@ def __call__(
     ) -> Callable[[F], F]:
         ...
 
-    # __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
-    @gather_metrics("cache_resource")
     def __call__(
         self,
         func: F | None = None,
@@ -168,6 +179,21 @@ def __call__(
         show_spinner: bool | str = True,
         suppress_st_warning=False,
         experimental_allow_widgets: bool = False,
+    ):
+        return self._decorator(
+            func,
+            show_spinner=show_spinner,
+            suppress_st_warning=suppress_st_warning,
+            experimental_allow_widgets=experimental_allow_widgets,
+        )
+
+    @staticmethod
+    def _decorator(
+        func: F | None = None,
+        *,
+        show_spinner: bool | str = True,
+        suppress_st_warning=False,
+        experimental_allow_widgets: bool = False,
     ):
         """Function decorator to store cached resources.
 
diff --git a/lib/tests/streamlit/runtime/caching/cache_data_api_test.py b/lib/tests/streamlit/runtime/caching/cache_data_api_test.py
index 93d3ec331dbb..494a71372193 100644
--- a/lib/tests/streamlit/runtime/caching/cache_data_api_test.py
+++ b/lib/tests/streamlit/runtime/caching/cache_data_api_test.py
@@ -215,6 +215,28 @@ def bar(x):
         self.assertEqual([0, 0, 0], foo_vals)
         self.assertEqual([0, 0], bar_vals)
 
+    def test_multiple_api_names(self):
+        """`st.experimental_memo` is effectively an alias for `st.cache_data`, and we
+        support both APIs while experimental_memo is being deprecated.
+        """
+        num_calls = [0]
+
+        def foo():
+            num_calls[0] += 1
+            return 42
+
+        # Annotate a function with both `cache_data` and `experimental_memo`.
+        cache_data_func = st.cache_data(foo)
+        memo_func = st.experimental_memo(foo)
+
+        # Call both versions of the function and assert the results.
+        self.assertEqual(42, cache_data_func())
+        self.assertEqual(42, memo_func())
+
+        # Because these decorators share the same cache, calling both functions
+        # results in just a single call to the decorated function.
+        self.assertEqual(1, num_calls[0])
+
 
 class CacheDataPersistTest(DeltaGeneratorTestCase):
     """st.cache_data disk persistence tests"""
diff --git a/lib/tests/streamlit/runtime/caching/cache_resource_api_test.py b/lib/tests/streamlit/runtime/caching/cache_resource_api_test.py
index 3fc74580fa59..532e9fe5ff46 100644
--- a/lib/tests/streamlit/runtime/caching/cache_resource_api_test.py
+++ b/lib/tests/streamlit/runtime/caching/cache_resource_api_test.py
@@ -72,6 +72,28 @@ def f():
         self.assertEqual(r1, [1, 1])
         self.assertEqual(r2, [1, 1])
 
+    def test_multiple_api_names(self):
+        """`st.experimental_singleton` is effectively an alias for `st.cache_resource`, and we
+        support both APIs while experimental_singleton is being deprecated.
+        """
+        num_calls = [0]
+
+        def foo():
+            num_calls[0] += 1
+            return 42
+
+        # Annotate a function with both `cache_resource` and `experimental_singleton`.
+        cache_resource_func = st.cache_resource(foo)
+        memo_func = st.experimental_singleton(foo)
+
+        # Call both versions of the function and assert the results.
+        self.assertEqual(42, cache_resource_func())
+        self.assertEqual(42, memo_func())
+
+        # Because these decorators share the same cache, calling both functions
+        # results in just a single call to the decorated function.
+        self.assertEqual(1, num_calls[0])
+
 
 class CacheResourceStatsProviderTest(unittest.TestCase):
     def setUp(self):
diff --git a/lib/tests/streamlit/runtime/metrics_util_test.py b/lib/tests/streamlit/runtime/metrics_util_test.py
index 049e7d19805b..58766d29fab8 100644
--- a/lib/tests/streamlit/runtime/metrics_util_test.py
+++ b/lib/tests/streamlit/runtime/metrics_util_test.py
@@ -252,8 +252,6 @@ def test_public_api_commands(self):
             "empty",
             "progress",
             "get_option",
-            "experimental_singleton",
-            "experimental_memo",
         }
 
         # Create a list of all public API names in the `st` module (minus

- The data team wants to track metrics on both the deprecated cache decorator names (`@st.experimental_memo`, `@st.experimental_singleton`) AND the new decorators names (`@st.cache_data`, `@st.cache_resource`)
- This PR parameterizes `CacheDataAPI` and `CacheResourceAPI` with the decorator metric name, to achieve the above.
- This means that we now have _two_ instances of both `CacheDataAPI` and `CacheResourceAPI` (one instance for both supported names). The underlying cache storage remains the same (that is, both `CacheDataAPI` instances share the same on-disk and in-memory cache storage; and the same is true of `CacheResourceAPI`).

Tests:
- `metrics_util_test.test_public_api_commands` no longer ignores st.experimental_memo/singleton, so these are now properly tracked as their own standalone metrics-gathering APIs
- `cache_data_api_test` and `cache_resource_api_test` now include tests asserting that decorator variants share the same storage.