Skip to content

Commit 0c7dc76

Browse files
author
Google Earth Engine Authors
committed
Support for external image uploads in eecli
PiperOrigin-RevId: 689482120
1 parent 6f51b8d commit 0c7dc76

File tree

2 files changed

+119
-43
lines changed

2 files changed

+119
-43
lines changed

python/ee/cli/commands.py

+31-16
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,22 @@ def run(
12611261
utils.wait_for_tasks(task_ids, args.timeout, log_progress=args.verbose)
12621262

12631263

1264+
def _using_v1alpha(func):
1265+
"""Decorator that temporarily switches over to the v1alpha API."""
1266+
1267+
def inner(
1268+
self, args: argparse.Namespace, config: utils.CommandLineConfig
1269+
) -> None:
1270+
# pylint: disable=protected-access
1271+
original = ee._cloud_api_utils.VERSION
1272+
ee._cloud_api_utils.VERSION = 'v1alpha'
1273+
func(self, args, config)
1274+
ee._cloud_api_utils.VERSION = original
1275+
# pylint: enable=protected-access
1276+
1277+
return inner
1278+
1279+
12641280
class TaskCommand(Dispatcher):
12651281
"""Prints information about or manages long-running tasks."""
12661282

@@ -1432,6 +1448,20 @@ def is_tf_record(path: str) -> bool:
14321448
return manifest
14331449

14341450

1451+
class UploadExternalImageCommand(UploadImageCommand):
1452+
name = 'external_image'
1453+
1454+
@_using_v1alpha
1455+
def run(
1456+
self, args: argparse.Namespace, config: utils.CommandLineConfig
1457+
) -> None:
1458+
"""Creates an external image synchronously."""
1459+
config.ee_init()
1460+
manifest = self.manifest_from_args(args)
1461+
name = ee.data.startExternalImageIngestion(manifest, args.force)['name']
1462+
print('Created asset %s' % name)
1463+
1464+
14351465
# TODO(user): update src_files help string when secondary files
14361466
# can be uploaded.
14371467
class UploadTableCommand:
@@ -1621,6 +1651,7 @@ class UploadCommand(Dispatcher):
16211651

16221652
COMMANDS = [
16231653
UploadImageCommand,
1654+
UploadExternalImageCommand,
16241655
UploadTableCommand,
16251656
]
16261657

@@ -1937,22 +1968,6 @@ class ModelCommand(Dispatcher):
19371968
COMMANDS = [PrepareModelCommand]
19381969

19391970

1940-
def _using_v1alpha(func):
1941-
"""Decorator that temporarily switches over to the v1alpha API."""
1942-
1943-
def inner(
1944-
self, args: argparse.Namespace, config: utils.CommandLineConfig
1945-
) -> None:
1946-
# pylint: disable=protected-access
1947-
original = ee._cloud_api_utils.VERSION
1948-
ee._cloud_api_utils.VERSION = 'v1alpha'
1949-
func(self, args, config)
1950-
ee._cloud_api_utils.VERSION = original
1951-
# pylint: enable=protected-access
1952-
1953-
return inner
1954-
1955-
19561971
class ProjectConfigGetCommand:
19571972
"""Prints the current project's ProjectConfig."""
19581973

python/ee/data.py

+88-27
Original file line numberDiff line numberDiff line change
@@ -1908,9 +1908,62 @@ def _prepare_and_run_export(
19081908
export_endpoint(project=_get_projects_path(), body=params),
19091909
num_retries=num_retries)
19101910

1911+
# TODO(user): use StrEnum when 3.11 is the min version
1912+
_INTERNAL_IMPORT = 'INTERNAL_IMPORT'
1913+
_EXTERNAL_IMPORT = 'EXTERNAL_IMPORT'
1914+
1915+
1916+
def _startIngestion(
1917+
request_id: Any,
1918+
params: Dict[str, Any],
1919+
allow_overwrite: bool = False,
1920+
import_mode: Optional[str] = _INTERNAL_IMPORT,
1921+
) -> Dict[str, Any]:
1922+
"""Starts an ingestion task or creates an external image."""
1923+
request = {
1924+
'imageManifest':
1925+
_cloud_api_utils.convert_params_to_image_manifest(params),
1926+
'overwrite':
1927+
allow_overwrite
1928+
}
1929+
1930+
# It's only safe to retry the request if there's a unique ID to make it
1931+
# idempotent.
1932+
num_retries = _max_retries if request_id else 0
1933+
1934+
image = _get_cloud_projects().image()
1935+
if import_mode == _INTERNAL_IMPORT:
1936+
import_request = image.import_(project=_get_projects_path(), body=request)
1937+
elif import_mode == _EXTERNAL_IMPORT:
1938+
import_request = image.importExternal(
1939+
project=_get_projects_path(), body=request
1940+
)
1941+
else:
1942+
raise ee_exception.EEException(
1943+
'{} is not a valid import mode'.format(import_mode)
1944+
)
1945+
1946+
result = _execute_cloud_call(
1947+
import_request,
1948+
num_retries=num_retries,
1949+
)
1950+
1951+
if import_mode == _INTERNAL_IMPORT:
1952+
return {
1953+
'id': _cloud_api_utils.convert_operation_name_to_task_id(
1954+
result['name']
1955+
),
1956+
'name': result['name'],
1957+
'started': 'OK',
1958+
}
1959+
else:
1960+
return {'name': request['imageManifest']['name']}
1961+
19111962

19121963
def startIngestion(
1913-
request_id: Any, params: Dict[str, Any], allow_overwrite: bool = False
1964+
request_id: Any,
1965+
params: Dict[str, Any],
1966+
allow_overwrite: bool = False,
19141967
) -> Dict[str, Any]:
19151968
"""Creates an image asset import task.
19161969
@@ -1923,7 +1976,7 @@ def startIngestion(
19231976
params: The object that describes the import task, which can
19241977
have these fields:
19251978
name (string) The destination asset id (e.g.,
1926-
"projects/earthengine-legacy/assets/users/foo/bar").
1979+
"projects/myproject/assets/foo/bar").
19271980
tilesets (array) A list of Google Cloud Storage source file paths
19281981
formatted like:
19291982
[{'sources': [
@@ -1942,31 +1995,39 @@ def startIngestion(
19421995
A dict with notes about the created task. This will include the ID for the
19431996
import task (under 'id'), which may be different from request_id.
19441997
"""
1945-
request = {
1946-
'imageManifest':
1947-
_cloud_api_utils.convert_params_to_image_manifest(params),
1948-
'requestId':
1949-
request_id,
1950-
'overwrite':
1951-
allow_overwrite
1952-
}
1998+
return _startIngestion(request_id, params, allow_overwrite, _INTERNAL_IMPORT)
19531999

1954-
# It's only safe to retry the request if there's a unique ID to make it
1955-
# idempotent.
1956-
num_retries = _max_retries if request_id else 0
1957-
operation = _execute_cloud_call(
1958-
_get_cloud_projects()
1959-
.image()
1960-
.import_(project=_get_projects_path(), body=request),
1961-
num_retries=num_retries,
1962-
)
1963-
return {
1964-
'id':
1965-
_cloud_api_utils.convert_operation_name_to_task_id(
1966-
operation['name']),
1967-
'name': operation['name'],
1968-
'started': 'OK',
1969-
}
2000+
2001+
def startExternalImageIngestion(
2002+
image_manifest: Dict[str, Any],
2003+
allow_overwrite: bool = False,
2004+
) -> Dict[str, Any]:
2005+
"""Creates an external image.
2006+
2007+
Args:
2008+
image_manifest: The object that describes the import task, which can
2009+
have these fields:
2010+
name (string) The destination asset id (e.g.,
2011+
"projects/myproject/assets/foo/bar").
2012+
tilesets (array) A list of Google Cloud Storage source file paths
2013+
formatted like:
2014+
[{'sources': [
2015+
{'uris': ['foo.tif', 'foo.prj']},
2016+
{'uris': ['bar.tif', 'bar.prj']},
2017+
]}]
2018+
Where path values correspond to source files' Google Cloud Storage
2019+
object names, e.g., 'gs://bucketname/filename.tif'
2020+
bands (array) An optional list of band names formatted like:
2021+
[{'id': 'R'}, {'id': 'G'}, {'id': 'B'}]
2022+
In general, this is a dict representation of an ImageManifest.
2023+
allow_overwrite: Whether the ingested image can overwrite an
2024+
existing version.
2025+
2026+
Returns:
2027+
The name of the created asset.
2028+
"""
2029+
return _startIngestion(
2030+
'unused', image_manifest, allow_overwrite, _EXTERNAL_IMPORT)
19702031

19712032

19722033
def startTableIngestion(
@@ -1983,7 +2044,7 @@ def startTableIngestion(
19832044
params: The object that describes the import task, which can
19842045
have these fields:
19852046
name (string) The destination asset id (e.g.,
1986-
"projects/earthengine-legacy/assets/users/foo/bar").
2047+
"projects/myproject/assets/foo/bar").
19872048
sources (array) A list of GCS (Google Cloud Storage) file paths
19882049
with optional character encoding formatted like this:
19892050
"sources":[{"uris":["gs://bucket/file.shp"],"charset":"UTF-8"}]

0 commit comments

Comments
 (0)