diff --git a/python/ee/__init__.py b/python/ee/__init__.py index 05736ecb0..89f791a99 100644 --- a/python/ee/__init__.py +++ b/python/ee/__init__.py @@ -134,15 +134,13 @@ def Authenticate( Args: authorization_code: An optional authorization code. - quiet: If true, do not require interactive prompts and force --no-browser - mode for gcloud-legacy. If false, never supply --no-browser. Default is - None, which autodetects the --no-browser setting. + quiet: If true, do not require interactive prompts. If false, never supply + --no-browser. Default is None, which autodetects the --no-browser setting. code_verifier: PKCE verifier to prevent auth code stealing. auth_mode: The authentication mode. One of: "colab" - use the Colab authentication flow; "notebook" - send user to notebook authenticator page; "gcloud" - use gcloud to obtain credentials; - "gcloud-legacy" - use legacy gcloud flow to obtain credentials; "localhost" - runs auth flow in local browser only; None - a default mode is chosen based on your environment. scopes: List of scopes to use for authentication. Defaults to [ @@ -185,8 +183,13 @@ def Initialize( credentials = data.get_persistent_credentials() if not project and credentials and hasattr(credentials, 'quota_project_id'): project = credentials.quota_project_id - # SDK credentials are not authorized for EE so a project must be given. - if not project and oauth.is_sdk_credentials(credentials): + if not project: + project = oauth.project_number_from_credentials(credentials) + # Convert falsey values to None. + project = project or None + # A project must be given, but SDK projects are not authorized for EE. + is_valid_project = project and not oauth.is_sdk_project(project) + if not is_valid_project: raise EEException(NO_PROJECT_EXCEPTION) data.initialize( diff --git a/python/ee/apitestcase.py b/python/ee/apitestcase.py index c85114728..6ec444acc 100644 --- a/python/ee/apitestcase.py +++ b/python/ee/apitestcase.py @@ -99,7 +99,7 @@ def InitializeApi(self): ee.data.getTableDownloadId = self._MockTableDownload # pylint: disable-next=protected-access ee.deprecation._FetchDataCatalogStac = self._MockFetchDataCatalogStac - ee.Initialize(None, '') + ee.Initialize(None, '', project='my-project') # We are mocking the url here so the unit tests are happy. def _MockMapId(self, params: Dict[str, Any]) -> Dict[str, str]: diff --git a/python/ee/cli/commands.py b/python/ee/cli/commands.py index 30c66702c..aea20c040 100644 --- a/python/ee/cli/commands.py +++ b/python/ee/cli/commands.py @@ -381,14 +381,14 @@ def __init__(self, parser: argparse.ArgumentParser): parser.add_argument( '--quiet', action='store_true', - help='Do not prompt for input, run gcloud-legacy in no-browser mode.') + help='Do not prompt for input.') parser.add_argument( '--code-verifier', help='PKCE verifier to prevent auth code stealing.') parser.add_argument( '--auth_mode', help='One of: notebook - use notebook authenticator; colab - use Colab' - ' authenticator; gcloud / gcloud-legacy - use gcloud;' + ' authenticator; gcloud - use gcloud;' ' localhost[:PORT|:0] - use local browser') parser.add_argument( '--scopes', help='Optional comma-separated list of scopes.') diff --git a/python/ee/oauth.py b/python/ee/oauth.py index 908cd935e..871183d3a 100644 --- a/python/ee/oauth.py +++ b/python/ee/oauth.py @@ -100,8 +100,14 @@ def get_credentials_arguments() -> Dict[str, Any]: def is_sdk_credentials(credentials: Optional[Any]) -> bool: + return is_sdk_project(project_number_from_credentials(credentials)) + + +def project_number_from_credentials( + credentials: Optional[Any], +) -> Optional[str]: client_id = credentials and getattr(credentials, 'client_id', None) - return is_sdk_project(_project_number_from_client_id(client_id)) + return _project_number_from_client_id(client_id) def is_sdk_project(project: str) -> bool: @@ -481,8 +487,8 @@ def authenticate( "localhost" - sends credentials to the Python environment on the same localhost as the browser. Does not work for remote shells. Default port is 8085; use localhost:N set port or localhost:0 to auto-select. - "gcloud-legacy" - use less convenient gcloud mode, for users without - cloud projects. + "gcloud-legacy" - included for legacy compatibility but not materially + different from "gcloud". "appdefault" - included for legacy compatibility but not necessary. ee.Initialize() will always check for application default credentials. None - a default mode is chosen based on your environment. diff --git a/python/ee/tests/ee_test.py b/python/ee/tests/ee_test.py index 2c3379a82..53952b0dc 100644 --- a/python/ee/tests/ee_test.py +++ b/python/ee/tests/ee_test.py @@ -40,8 +40,8 @@ def MockAlgorithms(): self.assertEqual(ee.ApiFunction._api, {}) self.assertTrue(ee.Image._initialized) - # Verify that ee.Initialize(None) does not override custom URLs. - ee.Initialize(None) + # Verify that ee.Initialize() without a URL does not override custom URLs. + ee.Initialize(None, project='my-project') self.assertTrue(ee.data._initialized) self.assertEqual(ee.data._api_base_url, 'foo/api') @@ -85,20 +85,25 @@ def CheckDataInit(**kwargs): ee.Initialize() google_creds = google_creds.with_quota_project(None) - expected_project = None - ee.Initialize() - self.assertEqual(5, inits.call_count) + with self.assertRaisesRegex(ee.EEException, '.*no project found..*'): + ee.Initialize() + self.assertEqual(4, inits.call_count) msg = 'Earth Engine API has not been used in project 764086051850 before' with moc(ee.ApiFunction, 'initialize', side_effect=ee.EEException(msg)): with self.assertRaisesRegex(ee.EEException, '.*no project found..*'): ee.Initialize() - cred_args['client_id'] = '764086051850-xxx' # dummy usable-auth client + cred_args['client_id'] = '123456789-xxx' cred_args['refresh_token'] = 'rt' + expected_project = '123456789' + ee.Initialize() + self.assertEqual(5, inits.call_count) + + cred_args['client_id'] = '764086051850-xxx' # dummy usable-auth client with self.assertRaisesRegex(ee.EEException, '.*no project found..*'): ee.Initialize() - self.assertEqual(6, inits.call_count) + self.assertEqual(5, inits.call_count) def testCallAndApply(self): """Verifies library initialization.""" @@ -122,7 +127,7 @@ def MockAlgorithms(): ee.data.getAlgorithms = MockAlgorithms - ee.Initialize(None) + ee.Initialize(None, project='my-project') image1 = ee.Image(1) image2 = ee.Image(2) expected = ee.Image( @@ -227,7 +232,7 @@ def MockAlgorithms(): ee.data.getAlgorithms = MockAlgorithms - ee.Initialize(None) + ee.Initialize(None, project='my-project') # Verify that the expected classes got generated. self.assertTrue(hasattr(ee, 'Array')) @@ -312,7 +317,7 @@ def MockAlgorithms(): } ee.data.getAlgorithms = MockAlgorithms - ee.Initialize(None) + ee.Initialize(None, project='my-project') # Try to cast something that's already of the right class. x = ee.Foo('argument') @@ -488,7 +493,7 @@ def MockAlgorithms(): ee.data.getAlgorithms = MockAlgorithms - ee.Initialize(None) + ee.Initialize(None, project='my-project') # The initialisation shouldn't blow up. self.assertTrue(callable(ee.Algorithms.Foo)) diff --git a/python/ee/tests/image_test.py b/python/ee/tests/image_test.py index de110aa61..706113372 100644 --- a/python/ee/tests/image_test.py +++ b/python/ee/tests/image_test.py @@ -306,7 +306,7 @@ def test_thumb_with_dimensions_region_crs(self): ) ), ) - self.assertEqual(kwargs['parent'], 'projects/earthengine-legacy') + self.assertEqual('projects/my-project', kwargs['parent']) def test_thumb_with_dimensions_region_json(self): # Try it with the region as a GeoJSON string. @@ -330,7 +330,7 @@ def test_thumb_with_dimensions_region_json(self): ) ), ) - self.assertEqual(kwargs['parent'], 'projects/earthengine-legacy') + self.assertEqual('projects/my-project', kwargs['parent']) def test_thumb_with_dimensions_list_coords(self): # Try it with the region as a list of coordinates. @@ -356,7 +356,7 @@ def test_thumb_with_dimensions_list_coords(self): ) ), ) - self.assertEqual(kwargs['parent'], 'projects/earthengine-legacy') + self.assertEqual('projects/my-project', kwargs['parent']) def test_thumb_with_dimensions_list_min_max(self): # Try it with the region as a list of coordinates. @@ -382,7 +382,7 @@ def test_thumb_with_dimensions_list_min_max(self): ) ), ) - self.assertEqual(kwargs['parent'], 'projects/earthengine-legacy') + self.assertEqual('projects/my-project', kwargs['parent']) def test_thumb_with_visualization_params(self): cloud_api_resource = mock.MagicMock() @@ -603,7 +603,7 @@ def test_download_url(self): kwargs['body']['expression'], ) self.assertEqual('ZIPPED_GEO_TIFF_PER_BAND', kwargs['body']['fileFormat']) - self.assertEqual('projects/earthengine-legacy', kwargs['parent']) + self.assertEqual('projects/my-project', kwargs['parent']) self.assertEqual( '/%s/thumbName:getPixels' % _cloud_api_utils.VERSION, url )