diff --git a/src/machinevisiontoolbox/Sources.py b/src/machinevisiontoolbox/Sources.py index efe1366a..5db0cbc8 100644 --- a/src/machinevisiontoolbox/Sources.py +++ b/src/machinevisiontoolbox/Sources.py @@ -1822,7 +1822,8 @@ class EarthView(ImageSource): """ Iterate images from GoogleEarth - :param key: Google API key, defaults to None + :param key: Google Maps API key, defaults to None (read from the + environment, see note below) :type key: str :param type: type of map (API ``maptype``): 'satellite' [default], 'map', 'roads', 'hybrid', and 'terrain'. :type type: str, optional @@ -1865,9 +1866,15 @@ class EarthView(ImageSource): `Getting started `_ .. note:: - - If the key is not passed in, a value is sought from the - environment variable ``GOOGLE_API_KEY``. - - Uses the `Google Maps Static API `_ + - If ``key`` is not passed in, it is read from the environment + variable ``GOOGLE_MAPS_API_KEY``, or failing that from + ``GOOGLE_API_KEY``. Prefer ``GOOGLE_MAPS_API_KEY``: Google's Gemini + libraries also read ``GOOGLE_API_KEY``, so using that name for a + Maps key can hand the wrong key to one or the other. + - Uses the `Google Maps Static API `_. + Restrict the key to that API only (Google Cloud console, *APIs & + Services → Credentials*), so a leaked key can't be used for + anything else, and consider a daily request quota to bound costs. :references: - |RVC3|, Section 11.1.6. @@ -1893,7 +1900,9 @@ def __init__( ) -> None: if key is None: - self.key = os.getenv("GOOGLE_API_KEY") + # GOOGLE_MAPS_API_KEY first; GOOGLE_API_KEY kept for backward + # compatibility (it's also the name Gemini libraries read) + self.key = os.getenv("GOOGLE_MAPS_API_KEY") or os.getenv("GOOGLE_API_KEY") else: self.key = key diff --git a/tests/test_image_sources.py b/tests/test_image_sources.py index 4f5cb2c0..1710fad8 100644 --- a/tests/test_image_sources.py +++ b/tests/test_image_sources.py @@ -607,5 +607,36 @@ def test_labelme_read(self): self.assertEqual(shapes[1].flags, {"hard": False}) +class TestEarthViewKey(unittest.TestCase): + """How EarthView finds its API key (no network access needed).""" + + def _earthview(self, env: dict[str, str], **kwargs): + from machinevisiontoolbox import EarthView + + clean = { + k: v + for k, v in os.environ.items() + if k not in ("GOOGLE_MAPS_API_KEY", "GOOGLE_API_KEY") + } + with patch.dict(os.environ, {**clean, **env}, clear=True): + return EarthView(**kwargs) + + def test_maps_key_preferred(self): + ev = self._earthview({"GOOGLE_MAPS_API_KEY": "maps", "GOOGLE_API_KEY": "other"}) + self.assertEqual(ev.key, "maps") + + def test_google_api_key_fallback(self): + ev = self._earthview({"GOOGLE_API_KEY": "legacy"}) + self.assertEqual(ev.key, "legacy") + + def test_explicit_key_wins(self): + ev = self._earthview({"GOOGLE_MAPS_API_KEY": "maps"}, key="explicit") + self.assertEqual(ev.key, "explicit") + + def test_no_key(self): + ev = self._earthview({}) + self.assertIsNone(ev.key) + + if __name__ == "__main__": unittest.main()