Repository: GoogleCloudPlatform/cloud-sql-python-connector
Pull request #1068 (2024-04-22 14:50:59)

refactor: rename Instance to RefreshAheadCache
Rename `Instance` to `RefreshAheadCache` to get ready for adding a `LazyRefresh` type in the future. This will improve naming so customers are not confused by `Instance` not reflecting a Cloud SQL instance and instead being a cache of connection info to connect to a Cloud SQL instance.

Also updated `RefreshAheadCache` docstrings to match [Google style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings).


Patch:
diff --git a/google/cloud/sql/connector/connector.py b/google/cloud/sql/connector/connector.py
index b35b0c2..049a4db 100755
--- a/google/cloud/sql/connector/connector.py
+++ b/google/cloud/sql/connector/connector.py
@@ -32,8 +32,8 @@ import google.cloud.sql.connector.asyncpg as asyncpg
 from google.cloud.sql.connector.client import CloudSQLClient
 from google.cloud.sql.connector.exceptions import ConnectorLoopError
 from google.cloud.sql.connector.exceptions import DnsNameResolutionError
-from google.cloud.sql.connector.instance import Instance
 from google.cloud.sql.connector.instance import IPTypes
+from google.cloud.sql.connector.instance import RefreshAheadCache
 import google.cloud.sql.connector.pg8000 as pg8000
 import google.cloud.sql.connector.pymysql as pymysql
 import google.cloud.sql.connector.pytds as pytds
@@ -113,7 +113,7 @@ class Connector:
                 asyncio.run_coroutine_threadsafe(generate_keys(), self._loop),
                 loop=self._loop,
             )
-        self._instances: Dict[str, Instance] = {}
+        self._cache: Dict[str, RefreshAheadCache] = {}
         self._client: Optional[CloudSQLClient] = None
 
         # initialize credentials
@@ -255,23 +255,23 @@ class Connector:
                 driver=driver,
             )
         enable_iam_auth = kwargs.pop("enable_iam_auth", self._enable_iam_auth)
-        if instance_connection_string in self._instances:
-            instance = self._instances[instance_connection_string]
-            if enable_iam_auth != instance._enable_iam_auth:
+        if instance_connection_string in self._cache:
+            cache = self._cache[instance_connection_string]
+            if enable_iam_auth != cache._enable_iam_auth:
                 raise ValueError(
                     f"connect() called with 'enable_iam_auth={enable_iam_auth}', "
-                    f"but previously used 'enable_iam_auth={instance._enable_iam_auth}'. "
+                    f"but previously used 'enable_iam_auth={cache._enable_iam_auth}'. "
                     "If you require both for your use case, please use a new "
                     "connector.Connector object."
                 )
         else:
-            instance = Instance(
+            cache = RefreshAheadCache(
                 instance_connection_string,
                 self._client,
                 self._keys,
                 enable_iam_auth,
             )
-            self._instances[instance_connection_string] = instance
+            self._cache[instance_connection_string] = cache
 
         connect_func = {
             "pymysql": pymysql.connect,
@@ -300,7 +300,7 @@ class Connector:
 
         # attempt to make connection to Cloud SQL instance
         try:
-            instance_data, ip_address = await instance.connect_info(ip_type)
+            instance_data, ip_address = await cache.connect_info(ip_type)
             # resolve DNS name into IP address for PSC
             if ip_type.value == "PSC":
                 addr_info = await self._loop.getaddrinfo(
@@ -339,7 +339,7 @@ class Connector:
 
         except Exception:
             # with any exception, we attempt a force refresh, then throw the error
-            await instance.force_refresh()
+            await cache.force_refresh()
             raise
 
     def __enter__(self) -> Any:
@@ -385,11 +385,9 @@ class Connector:
             self._thread.join()
 
     async def close_async(self) -> None:
-        """Helper function to cancel Instances' tasks
+        """Helper function to cancel the cache's tasks
         and close aiohttp.ClientSession."""
-        await asyncio.gather(
-            *[instance.close() for instance in self._instances.values()]
-        )
+        await asyncio.gather(*[cache.close() for cache in self._cache.values()])
         if self._client:
             await self._client.close()
 
diff --git a/google/cloud/sql/connector/instance.py b/google/cloud/sql/connector/instance.py
index 86e33f1..d10c0e7 100644
--- a/google/cloud/sql/connector/instance.py
+++ b/google/cloud/sql/connector/instance.py
@@ -144,32 +144,13 @@ class ConnectionInfo:
         )
 
 
-class Instance:
-    """A class to manage the details of the connection to a Cloud SQL
-    instance, including refreshing the credentials.
-
-    :param instance_connection_string:
-        The Google Cloud SQL Instance's connection
-        string.
-    :type instance_connection_string: str
-
-    :param enable_iam_auth
-        Enables automatic IAM database authentication for Postgres or MySQL
-        instances.
-    :type enable_iam_auth: bool
-    """
-
-    _enable_iam_auth: bool
-    _keys: asyncio.Future
-    _instance_connection_string: str
-    _instance: str
-    _project: str
-    _region: str
+class RefreshAheadCache:
+    """Cache that refreshes connection info in the background prior to expiration.
 
-    _refresh_rate_limiter: AsyncRateLimiter
-    _refresh_in_progress: asyncio.locks.Event
-    _current: asyncio.Task  # task wraps coroutine that returns ConnectionInfo
-    _next: asyncio.Task  # task wraps coroutine that returns another task
+    Background tasks are used to schedule refresh attempts to get a new
+    ephemeral certificate and Cloud SQL metadata (IP addresses, etc.) ahead of
+    expiration.
+    """
 
     def __init__(
         self,
@@ -178,6 +159,18 @@ class Instance:
         keys: asyncio.Future,
         enable_iam_auth: bool = False,
     ) -> None:
+        """Initializes a RefreshAheadCache instance.
+
+        Args:
+            instance_connection_string (str): The Cloud SQL Instance's
+                connection string (also known as an instance connection name).
+            client (CloudSQLClient): The Cloud SQL Client instance.
+            keys (asyncio.Future): A future to the client's public-private key
+                pair.
+            enable_iam_auth (bool): Enables automatic IAM database authentication
+                (Postgres and MySQL) as the default authentication method for all
+                connections.
+        """
         # validate and parse instance connection name
         self._project, self._region, self._instance = _parse_instance_connection_name(
             instance_connection_string
@@ -192,8 +185,8 @@ class Instance:
             rate=1 / 30,
         )
         self._refresh_in_progress = asyncio.locks.Event()
-        self._current = self._schedule_refresh(0)
-        self._next = self._current
+        self._current: asyncio.Task = self._schedule_refresh(0)
+        self._next: asyncio.Task = self._current
 
     async def force_refresh(self) -> None:
         """
@@ -211,10 +204,11 @@ class Instance:
         """Retrieves instance metadata and ephemeral certificate from the
         Cloud SQL Instance.
 
-        :rtype: ConnectionInfo
-        :returns: A dataclass containing a string representing the ephemeral certificate, a dict
-            containing the instances IP adresses, a string representing a PEM-encoded private key
-            and a string representing a PEM-encoded certificate authority.
+        Returns:
+            A ConnectionInfo instance containing a string representing the
+            ephemeral certificate, a dict containing the instances IP adresses,
+            a string representing a PEM-encoded private key and a string
+            representing a PEM-encoded certificate authority.
         """
         self._refresh_in_progress.set()
         logger.debug(
@@ -290,15 +284,14 @@ class Instance:
         """
         Schedule task to sleep and then perform refresh to get ConnectionInfo.
 
-        :type delay: int
-        :param delay
-            Time in seconds to sleep before running _perform_refresh.
+        Args:
+            delay (int): Time in seconds to sleep before performing a refresh.
 
-        :rtype: asyncio.Task
-        :returns: A Task representing the scheduled _perform_refresh.
+        Returns:
+            An asyncio.Task representing the scheduled refresh.
         """
 
-        async def _refresh_task(self: Instance, delay: int) -> ConnectionInfo:
+        async def _refresh_task(self: RefreshAheadCache, delay: int) -> ConnectionInfo:
             """
             A coroutine that sleeps for the specified amount of time before
             running _perform_refresh.
@@ -349,16 +342,14 @@ class Instance:
         """Retrieve instance metadata and ip address required
         for making connection to Cloud SQL instance.
 
-        :type ip_type: IPTypes
-        :param ip_type: Enum specifying whether to look for public
-            or private IP address.
-
-        :rtype instance_data: ConnectionInfo
-        :returns: Instance metadata for Cloud SQL instance.
+        Args:
+            ip_type (IPTypes): Enum specifying type of IP address to lookup and
+                use for connection.
 
-        :rtype ip_address: str
-        :returns: A string representing the IP address of
-            the given Cloud SQL instance.
+        Returns:
+            A tuple with the first item being the ConnectionInfo instance for
+            establishing the connection, and the second item being the IP
+            address of the Cloud SQL instance matching the specified IP type.
         """
         logger.debug(
             f"['{self._instance_connection_string}']: Entered connect_info method"


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 8f3d780..1be12fb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -25,7 +25,7 @@ from unit.mocks import FakeCredentials  # type: ignore
 from unit.mocks import FakeCSQLInstance  # type: ignore
 
 from google.cloud.sql.connector.client import CloudSQLClient
-from google.cloud.sql.connector.instance import Instance
+from google.cloud.sql.connector.instance import RefreshAheadCache
 from google.cloud.sql.connector.utils import generate_keys
 
 SCOPES = ["https://www.googleapis.com/auth/sqlservice.admin"]
@@ -137,12 +137,12 @@ async def fake_client(
 
 
 @pytest.fixture
-async def instance(fake_client: CloudSQLClient) -> AsyncGenerator[Instance, None]:
+async def cache(fake_client: CloudSQLClient) -> AsyncGenerator[RefreshAheadCache, None]:
     keys = asyncio.create_task(generate_keys())
-    instance = Instance(
+    cache = RefreshAheadCache(
         "test-project:test-region:test-instance",
         client=fake_client,
         keys=keys,
     )
-    yield instance
-    await instance.close()
+    yield cache
+    await cache.close()
diff --git a/tests/system/test_connector_object.py b/tests/system/test_connector_object.py
index 84d305e..6549702 100644
--- a/tests/system/test_connector_object.py
+++ b/tests/system/test_connector_object.py
@@ -83,11 +83,11 @@ def test_multiple_connectors() -> None:
             conn.execute(sqlalchemy.text("SELECT 1"))
 
         instance_connection_string = os.environ["MYSQL_CONNECTION_NAME"]
-        assert instance_connection_string in first_connector._instances
-        assert instance_connection_string in second_connector._instances
+        assert instance_connection_string in first_connector._cache
+        assert instance_connection_string in second_connector._cache
         assert (
-            first_connector._instances[instance_connection_string]
-            != second_connector._instances[instance_connection_string]
+            first_connector._cache[instance_connection_string]
+            != second_connector._cache[instance_connection_string]
         )
     except Exception as e:
         logging.exception("Failed to connect with multiple Connector objects!", e)
diff --git a/tests/unit/test_connector.py b/tests/unit/test_connector.py
index 89e1eb7..f6e6fc6 100644
--- a/tests/unit/test_connector.py
+++ b/tests/unit/test_connector.py
@@ -25,18 +25,18 @@ from google.cloud.sql.connector import create_async_connector
 from google.cloud.sql.connector import IPTypes
 from google.cloud.sql.connector.client import CloudSQLClient
 from google.cloud.sql.connector.exceptions import ConnectorLoopError
-from google.cloud.sql.connector.instance import Instance
+from google.cloud.sql.connector.instance import RefreshAheadCache
 
 
 def test_connect_enable_iam_auth_error(
-    fake_credentials: Credentials, instance: Instance
+    fake_credentials: Credentials, cache: RefreshAheadCache
 ) -> None:
     """Test that calling connect() with different enable_iam_auth
     argument values throws error."""
     connect_string = "test-project:test-region:test-instance"
     connector = Connector(credentials=fake_credentials)
-    # set instance
-    connector._instances[connect_string] = instance
+    # set cache
+    connector._cache[connect_string] = cache
     # try to connect using enable_iam_auth=True, should raise error
     with pytest.raises(ValueError) as exc_info:
         connector.connect(connect_string, "pg8000", enable_iam_auth=True)
@@ -46,8 +46,8 @@ def test_connect_enable_iam_auth_error(
         "If you require both for your use case, please use a new "
         "connector.Connector object."
     )
-    # remove instance to avoid destructor warnings
-    connector._instances = {}
+    # remove cache entrry to avoid destructor warnings
+    connector._cache = {}
 
 
 def test_connect_with_unsupported_driver(fake_credentials: Credentials) -> None:
diff --git a/tests/unit/test_instance.py b/tests/unit/test_instance.py
index c083d5b..136fddb 100644
--- a/tests/unit/test_instance.py
+++ b/tests/unit/test_instance.py
@@ -30,8 +30,8 @@ from google.cloud.sql.connector.exceptions import AutoIAMAuthNotSupported
 from google.cloud.sql.connector.exceptions import CloudSQLIPTypeError
 from google.cloud.sql.connector.instance import _parse_instance_connection_name
 from google.cloud.sql.connector.instance import ConnectionInfo
-from google.cloud.sql.connector.instance import Instance
 from google.cloud.sql.connector.instance import IPTypes
+from google.cloud.sql.connector.instance import RefreshAheadCache
 from google.cloud.sql.connector.rate_limiter import AsyncRateLimiter
 from google.cloud.sql.connector.utils import generate_keys
 
@@ -71,127 +71,127 @@ def test_parse_instance_connection_name_bad_conn_name() -> None:
 
 @pytest.mark.asyncio
 async def test_Instance_init(
-    instance: Instance,
+    cache: RefreshAheadCache,
 ) -> None:
     """
-    Test to check whether the __init__ method of Instance
+    Test to check whether the __init__ method of RefreshAheadCache
     can tell if the connection string that's passed in is formatted correctly.
     """
     assert (
-        instance._project == "test-project"
-        and instance._region == "test-region"
-        and instance._instance == "test-instance"
+        cache._project == "test-project"
+        and cache._region == "test-region"
+        and cache._instance == "test-instance"
     )
-    assert instance._enable_iam_auth is False
+    assert cache._enable_iam_auth is False
 
 
 @pytest.mark.asyncio
 async def test_schedule_refresh_replaces_result(
-    instance: Instance, test_rate_limiter: AsyncRateLimiter
+    cache: RefreshAheadCache, test_rate_limiter: AsyncRateLimiter
 ) -> None:
     """
     Test to check whether _schedule_refresh replaces a valid result with another valid result
     """
     # allow more frequent refreshes for tests
-    setattr(instance, "_refresh_rate_limiter", test_rate_limiter)
+    setattr(cache, "_refresh_rate_limiter", test_rate_limiter)
 
     # stub _perform_refresh to return a "valid" MockMetadata object
-    setattr(instance, "_perform_refresh", mocks.instance_metadata_success)
+    setattr(cache, "_perform_refresh", mocks.instance_metadata_success)
 
-    old_metadata = await instance._current
+    old_metadata = await cache._current
 
     # schedule refresh immediately and await it
-    refresh_task = instance._schedule_refresh(0)
+    refresh_task = cache._schedule_refresh(0)
     refresh_metadata = await refresh_task
 
     # check that current metadata has been replaced with refresh metadata
-    assert instance._current.result() == refresh_metadata
-    assert old_metadata != instance._current.result()
-    assert isinstance(instance._current.result(), mocks.MockMetadata)
-    # cleanup instance
-    await instance.close()
+    assert cache._current.result() == refresh_metadata
+    assert old_metadata != cache._current.result()
+    assert isinstance(cache._current.result(), mocks.MockMetadata)
+    # cleanup cache
+    await cache.close()
 
 
 @pytest.mark.asyncio
 async def test_schedule_refresh_wont_replace_valid_result_with_invalid(
-    instance: Instance, test_rate_limiter: AsyncRateLimiter
+    cache: RefreshAheadCache, test_rate_limiter: AsyncRateLimiter
 ) -> None:
     """
     Test to check whether _perform_refresh won't replace a valid _current
     value with an invalid one
     """
     # allow more frequent refreshes for tests
-    setattr(instance, "_refresh_rate_limiter", test_rate_limiter)
+    setattr(cache, "_refresh_rate_limiter", test_rate_limiter)
 
-    await instance._current
-    old_task = instance._current
+    await cache._current
+    old_task = cache._current
 
     # stub _perform_refresh to throw an error
-    setattr(instance, "_perform_refresh", mocks.instance_metadata_error)
+    setattr(cache, "_perform_refresh", mocks.instance_metadata_error)
 
     # schedule refresh immediately
-    refresh_task = instance._schedule_refresh(0)
+    refresh_task = cache._schedule_refresh(0)
 
     # wait for invalid refresh to finish
     with pytest.raises(mocks.BadRefresh):
         assert await refresh_task
 
     # check that invalid refresh did not replace valid current metadata
-    assert instance._current == old_task
-    assert isinstance(await instance._current, ConnectionInfo)
+    assert cache._current == old_task
+    assert isinstance(await cache._current, ConnectionInfo)
 
 
 @pytest.mark.asyncio
 async def test_schedule_refresh_replaces_invalid_result(
-    instance: Instance, test_rate_limiter: AsyncRateLimiter
+    cache: RefreshAheadCache, test_rate_limiter: AsyncRateLimiter
 ) -> None:
     """
     Test to check whether _perform_refresh will replace an invalid refresh result with
     a valid one
     """
     # allow more frequent refreshes for tests
-    setattr(instance, "_refresh_rate_limiter", test_rate_limiter)
+    setattr(cache, "_refresh_rate_limiter", test_rate_limiter)
 
     # stub _perform_refresh to throw an error
-    setattr(instance, "_perform_refresh", mocks.instance_metadata_error)
+    setattr(cache, "_perform_refresh", mocks.instance_metadata_error)
 
     # set current to invalid data (error)
-    instance._current = instance._schedule_refresh(0)
+    cache._current = cache._schedule_refresh(0)
 
     # check that current is now invalid (error)
     with pytest.raises(mocks.BadRefresh):
-        assert await instance._current
+        assert await cache._current
 
     # stub _perform_refresh to return a valid MockMetadata instance
-    setattr(instance, "_perform_refresh", mocks.instance_metadata_success)
+    setattr(cache, "_perform_refresh", mocks.instance_metadata_success)
 
     # schedule refresh immediately and await it
-    refresh_task = instance._schedule_refresh(0)
+    refresh_task = cache._schedule_refresh(0)
     refresh_metadata = await refresh_task
 
     # check that current is now valid MockMetadata
-    assert instance._current.result() == refresh_metadata
-    assert isinstance(instance._current.result(), mocks.MockMetadata)
+    assert cache._current.result() == refresh_metadata
+    assert isinstance(cache._current.result(), mocks.MockMetadata)
 
 
 @pytest.mark.asyncio
 async def test_force_refresh_cancels_pending_refresh(
-    instance: Instance,
+    cache: RefreshAheadCache,
     test_rate_limiter: AsyncRateLimiter,
 ) -> None:
     """
     Test that force_refresh cancels pending task if refresh_in_progress event is not set.
     """
     # allow more frequent refreshes for tests
-    setattr(instance, "_refresh_rate_limiter", test_rate_limiter)
+    setattr(cache, "_refresh_rate_limiter", test_rate_limiter)
     # make sure initial refresh is finished
-    await instance._current
+    await cache._current
     # since the pending refresh isn't for another 55 min, the refresh_in_progress event
     # shouldn't be set
-    pending_refresh = instance._next
-    assert instance._refresh_in_progress.is_set() is False
+    pending_refresh = cache._next
+    assert cache._refresh_in_progress.is_set() is False
 
-    await instance.force_refresh()
+    await cache.force_refresh()
 
     # pending_refresh has to be awaited for it to raised as cancelled
     with pytest.raises(asyncio.CancelledError):
@@ -199,35 +199,35 @@ async def test_force_refresh_cancels_pending_refresh(
 
     # verify pending_refresh has now been cancelled
     assert pending_refresh.cancelled() is True
-    assert isinstance(await instance._current, ConnectionInfo)
+    assert isinstance(await cache._current, ConnectionInfo)
 
 
 @pytest.mark.asyncio
-async def test_Instance_close(instance: Instance) -> None:
+async def test_RefreshAheadCache_close(cache: RefreshAheadCache) -> None:
     """
-    Test that Instance's close method
+    Test that RefreshAheadCache's close method
     cancels tasks and closes ClientSession.
     """
     # make sure current metadata task is done
-    await instance._current
-    assert instance._current.cancelled() is False
-    assert instance._next.cancelled() is False
+    await cache._current
+    assert cache._current.cancelled() is False
+    assert cache._next.cancelled() is False
     # run close() to cancel tasks and close ClientSession
-    await instance.close()
+    await cache.close()
     # verify tasks are cancelled and ClientSession is closed
-    assert (instance._current.done() or instance._current.cancelled()) is True
-    assert instance._next.cancelled() is True
+    assert (cache._current.done() or cache._current.cancelled()) is True
+    assert cache._next.cancelled() is True
 
 
 @pytest.mark.asyncio
 async def test_perform_refresh(
-    instance: Instance,
+    cache: RefreshAheadCache,
     fake_instance: mocks.FakeCSQLInstance,
 ) -> None:
     """
     Test that _perform_refresh returns valid ConnectionInfo object.
     """
-    instance_metadata = await instance._perform_refresh()
+    instance_metadata = await cache._perform_refresh()
 
     # verify instance metadata object is returned
     assert isinstance(instance_metadata, ConnectionInfo)
@@ -237,7 +237,7 @@ async def test_perform_refresh(
 
 @pytest.mark.asyncio
 async def test_perform_refresh_expiration(
-    instance: Instance,
+    cache: RefreshAheadCache,
 ) -> None:
     """
     Test that _perform_refresh returns ConnectionInfo with proper expiration.
@@ -250,11 +250,11 @@ async def test_perform_refresh_expiration(
         minutes=1
     )
     credentials = mocks.FakeCredentials(token="my-token", expiry=expiration)
-    setattr(instance, "_enable_iam_auth", True)
+    setattr(cache, "_enable_iam_auth", True)
     # set downscoped credential to mock
     with patch("google.cloud.sql.connector.client._downscope_credentials") as mock_auth:
         mock_auth.return_value = credentials
-        instance_metadata = await instance._perform_refresh()
+        instance_metadata = await cache._perform_refresh()
     mock_auth.assert_called_once()
     # verify instance metadata object is returned
     assert isinstance(instance_metadata, ConnectionInfo)
@@ -264,12 +264,12 @@ async def test_perform_refresh_expiration(
 
 @pytest.mark.asyncio
 async def test_connect_info(
-    instance: Instance,
+    cache: RefreshAheadCache,
 ) -> None:
     """
     Test that connect_info returns current metadata and preferred IP.
     """
-    instance_metadata, ip_addr = await instance.connect_info(IPTypes.PUBLIC)
+    instance_metadata, ip_addr = await cache.connect_info(IPTypes.PUBLIC)
 
     # verify metadata and ip address
     assert isinstance(instance_metadata, ConnectionInfo)
@@ -277,12 +277,12 @@ async def test_connect_info(
 
 
 @pytest.mark.asyncio
-async def test_get_preferred_ip_CloudSQLIPTypeError(instance: Instance) -> None:
+async def test_get_preferred_ip_CloudSQLIPTypeError(cache: RefreshAheadCache) -> None:
     """
     Test that get_preferred_ip throws proper CloudSQLIPTypeError
     when missing Public or Private IP addresses.
     """
-    instance_metadata: ConnectionInfo = await instance._current
+    instance_metadata: ConnectionInfo = await cache._current
     instance_metadata.ip_addrs = {"PRIVATE": "1.1.1.1"}
     # test error when Public IP is missing
     with pytest.raises(CloudSQLIPTypeError):
@@ -331,13 +331,13 @@ async def test_ClientResponseError(
             ),
             repeat=True,
         )
-        instance = Instance(
+        cache = RefreshAheadCache(
             "my-project:my-region:my-instance",
             client,
             keys,
         )
         try:
-            await instance._current
+            await cache._current
         except ClientResponseError as e:
             assert e.status == 403
             assert (
@@ -347,7 +347,7 @@ async def test_ClientResponseError(
                 "'Cloud SQL Client' role has been granted to IAM principal."
             )
         finally:
-            await instance.close()
+            await cache.close()
             await client.close()
 
 
@@ -359,11 +359,11 @@ async def test_AutoIAMAuthNotSupportedError(fake_client: CloudSQLClient) -> None
     """
     # generate client key pair
     keys = asyncio.create_task(generate_keys())
-    instance = Instance(
+    cache = RefreshAheadCache(
         "test-project:test-region:sqlserver-instance",
         client=fake_client,
         keys=keys,
         enable_iam_auth=True,
     )
     with pytest.raises(AutoIAMAuthNotSupported):
-        await instance._current
+        await cache._current