ee-client 2.6.0__tar.gz → 2.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ee_client-2.6.0 → ee_client-2.6.2}/PKG-INFO +1 -1
- {ee_client-2.6.0 → ee_client-2.6.2}/ee_client.egg-info/PKG-INFO +1 -1
- {ee_client-2.6.0 → ee_client-2.6.2}/ee_client.egg-info/SOURCES.txt +2 -1
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/__init__.py +1 -1
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/export/image.py +7 -1
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/tasks.py +6 -3
- {ee_client-2.6.0 → ee_client-2.6.2}/pyproject.toml +2 -2
- ee_client-2.6.2/tests/test_export_image.py +89 -0
- ee_client-2.6.2/tests/test_tasks.py +60 -0
- ee_client-2.6.0/tests/test_export_image.py +0 -31
- {ee_client-2.6.0 → ee_client-2.6.2}/LICENSE +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/README.rst +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/ee_client.egg-info/dependency_links.txt +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/ee_client.egg-info/requires.txt +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/ee_client.egg-info/top_level.txt +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/cache.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/client.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/data.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/exceptions.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/export/__init__.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/export/table.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/helpers.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/interfaces/__init__.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/interfaces/export.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/interfaces/operations.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/interfaces/tasks.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/models.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/oauth_app.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/eeclient/sepal_credential_mixin.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/setup.cfg +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/tests/test_cache.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/tests/test_client.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/tests/test_data.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/tests/test_export_table.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/tests/test_integration_get_assets.py +0 -0
- {ee_client-2.6.0 → ee_client-2.6.2}/tests/test_models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.2
|
|
4
4
|
Summary: extends the capabilities of the earthengine-api by providing custom session management and client interactions
|
|
5
5
|
Author-email: Daniel Guerrero <dfgm2006@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/dfguerrerom/ee-client
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.2
|
|
4
4
|
Summary: extends the capabilities of the earthengine-api by providing custom session management and client interactions
|
|
5
5
|
Author-email: Daniel Guerrero <dfgm2006@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/dfguerrerom/ee-client
|
|
@@ -185,7 +185,13 @@ async def _export_image(
|
|
|
185
185
|
image, updated_image_params, dimensions_consumed = image._apply_crs_and_affine(
|
|
186
186
|
image_params
|
|
187
187
|
)
|
|
188
|
-
|
|
188
|
+
# _apply_selection_and_scale returns a new image wrapped in
|
|
189
|
+
# clipToBoundsAndScale(geometry=region); discarding the return value would
|
|
190
|
+
# serialize the original unbounded image and cause GEE to reject the task
|
|
191
|
+
# with "Unable to export unbounded image." (issue #20)
|
|
192
|
+
image, _ = image._apply_selection_and_scale(
|
|
193
|
+
updated_image_params, dimensions_consumed
|
|
194
|
+
)
|
|
189
195
|
|
|
190
196
|
expression = serializer.encode(image, for_cloud_api=True)
|
|
191
197
|
request_params["expression"] = expression
|
|
@@ -24,9 +24,12 @@ class CamelCaseModel(BaseModel):
|
|
|
24
24
|
|
|
25
25
|
class Stage(CamelCaseModel):
|
|
26
26
|
display_name: str
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# The Cloud API omits these fields on a stage until that stage has
|
|
28
|
+
# reported progress, so they must be optional to avoid breaking polling
|
|
29
|
+
# of a freshly-RUNNING task.
|
|
30
|
+
complete_work_units: Optional[int] = None
|
|
31
|
+
total_work_units: Optional[str] = None
|
|
32
|
+
description: Optional[str] = None
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class TaskMetadata(CamelCaseModel):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ee-client"
|
|
7
|
-
version = "2.6.
|
|
7
|
+
version = "2.6.2"
|
|
8
8
|
description = "extends the capabilities of the earthengine-api by providing custom session management and client interactions"
|
|
9
9
|
readme = { file = "README.rst", content-type = "text/x-rst" }
|
|
10
10
|
authors = [
|
|
@@ -66,7 +66,7 @@ branch = true
|
|
|
66
66
|
[tool.commitizen]
|
|
67
67
|
tag_format = "v$major.$minor.$patch$prerelease"
|
|
68
68
|
update_changelog_on_bump = false
|
|
69
|
-
version = "2.6.
|
|
69
|
+
version = "2.6.2"
|
|
70
70
|
version_files = [
|
|
71
71
|
"pyproject.toml:version",
|
|
72
72
|
"eeclient/__init__.py:__version__",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from eeclient.export.image import (
|
|
6
|
+
DriveDestination,
|
|
7
|
+
DriveOptions,
|
|
8
|
+
ImageFileFormat,
|
|
9
|
+
_export_image,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_drive_options_accepts_legacy_geotiff_alias():
|
|
14
|
+
opts = DriveOptions(
|
|
15
|
+
file_format="GeoTIFF",
|
|
16
|
+
drive_destination=DriveDestination(filename_prefix="out"),
|
|
17
|
+
)
|
|
18
|
+
assert opts.file_format == ImageFileFormat.GEO_TIFF
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_drive_options_accepts_canonical_value():
|
|
22
|
+
opts = DriveOptions(
|
|
23
|
+
file_format="GEO_TIFF",
|
|
24
|
+
drive_destination=DriveDestination(filename_prefix="out"),
|
|
25
|
+
)
|
|
26
|
+
assert opts.file_format == ImageFileFormat.GEO_TIFF
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_drive_options_rejects_unknown_string():
|
|
30
|
+
with pytest.raises(ValueError):
|
|
31
|
+
DriveOptions(
|
|
32
|
+
file_format="NotAFormat",
|
|
33
|
+
drive_destination=DriveDestination(filename_prefix="out"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_export_image_uses_clipped_image_from_selection_and_scale():
|
|
39
|
+
"""Regression test for issue #20.
|
|
40
|
+
|
|
41
|
+
`ee.Image._apply_selection_and_scale` returns a new image wrapped in
|
|
42
|
+
`clipToBoundsAndScale(geometry=region)`. The export flow must serialize
|
|
43
|
+
that clipped image, not the original, or GEE rejects the task as
|
|
44
|
+
"Unable to export unbounded image".
|
|
45
|
+
"""
|
|
46
|
+
original_image = MagicMock(name="original_image")
|
|
47
|
+
crs_applied_image = MagicMock(name="crs_applied_image")
|
|
48
|
+
clipped_image = MagicMock(name="clipped_image")
|
|
49
|
+
|
|
50
|
+
original_image._apply_crs_and_affine.return_value = (
|
|
51
|
+
crs_applied_image,
|
|
52
|
+
{"region": "geom", "scale": 30},
|
|
53
|
+
False,
|
|
54
|
+
)
|
|
55
|
+
crs_applied_image._apply_selection_and_scale.return_value = (
|
|
56
|
+
clipped_image,
|
|
57
|
+
{},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
client = MagicMock()
|
|
61
|
+
client.rest_call = AsyncMock(
|
|
62
|
+
return_value={"name": "projects/p/operations/op", "metadata": {}}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
drive_options = DriveOptions(
|
|
66
|
+
file_format=ImageFileFormat.GEO_TIFF,
|
|
67
|
+
drive_destination=DriveDestination(filename_prefix="out"),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
with (
|
|
71
|
+
patch("eeclient.export.image.serializer.encode") as mock_encode,
|
|
72
|
+
patch("eeclient.export.image.Task.model_validate") as mock_validate,
|
|
73
|
+
):
|
|
74
|
+
mock_encode.return_value = {"result": "encoded"}
|
|
75
|
+
mock_validate.return_value = MagicMock()
|
|
76
|
+
|
|
77
|
+
await _export_image(
|
|
78
|
+
client=client,
|
|
79
|
+
image=original_image,
|
|
80
|
+
drive_options=drive_options,
|
|
81
|
+
region="geom",
|
|
82
|
+
scale=30,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# The serialized expression must be the clipped image, not the original
|
|
86
|
+
# or the intermediate crs-applied image.
|
|
87
|
+
mock_encode.assert_called_once()
|
|
88
|
+
encoded_image = mock_encode.call_args.args[0]
|
|
89
|
+
assert encoded_image is clipped_image
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Tests for the Task / Stage models in eeclient.tasks."""
|
|
2
|
+
|
|
3
|
+
from eeclient.tasks import Stage, Task
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_stage_allows_missing_work_units():
|
|
7
|
+
"""The Cloud API omits completeWorkUnits/totalWorkUnits on a stage until
|
|
8
|
+
that stage reports progress; the model must accept this."""
|
|
9
|
+
stage = Stage.model_validate(
|
|
10
|
+
{
|
|
11
|
+
"displayName": "Create List of Assets",
|
|
12
|
+
"description": "Listing of temporary files.",
|
|
13
|
+
}
|
|
14
|
+
)
|
|
15
|
+
assert stage.display_name == "Create List of Assets"
|
|
16
|
+
assert stage.complete_work_units is None
|
|
17
|
+
assert stage.total_work_units is None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_stage_allows_missing_description():
|
|
21
|
+
"""Some stages omit `description` entirely before reporting progress."""
|
|
22
|
+
stage = Stage.model_validate({"displayName": "Write Files"})
|
|
23
|
+
assert stage.display_name == "Write Files"
|
|
24
|
+
assert stage.description is None
|
|
25
|
+
assert stage.complete_work_units is None
|
|
26
|
+
assert stage.total_work_units is None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_task_validates_with_incomplete_stages():
|
|
30
|
+
"""Regression for issue #22: polling a freshly-RUNNING export returns
|
|
31
|
+
stages without completeWorkUnits / totalWorkUnits and must not raise."""
|
|
32
|
+
payload = {
|
|
33
|
+
"name": "projects/ee-project/operations/ABC123",
|
|
34
|
+
"metadata": {
|
|
35
|
+
"@type": "type.googleapis.com/google.earthengine.v1.OperationMetadata",
|
|
36
|
+
"state": "RUNNING",
|
|
37
|
+
"description": "my-export",
|
|
38
|
+
"priority": 100,
|
|
39
|
+
"createTime": "2026-05-13T12:00:00Z",
|
|
40
|
+
"type": "EXPORT_IMAGE",
|
|
41
|
+
"stages": [
|
|
42
|
+
{
|
|
43
|
+
"displayName": "Create List of Assets",
|
|
44
|
+
"description": "Listing of temporary files.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"displayName": "Write Files",
|
|
48
|
+
"description": "Writing files to the export destination.",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
"done": False,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
task = Task.model_validate(payload)
|
|
56
|
+
assert task.id == "ABC123"
|
|
57
|
+
assert task.metadata.stages is not None
|
|
58
|
+
assert len(task.metadata.stages) == 2
|
|
59
|
+
assert task.metadata.stages[0].complete_work_units is None
|
|
60
|
+
assert task.metadata.stages[0].total_work_units is None
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from eeclient.export.image import (
|
|
4
|
-
DriveDestination,
|
|
5
|
-
DriveOptions,
|
|
6
|
-
ImageFileFormat,
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_drive_options_accepts_legacy_geotiff_alias():
|
|
11
|
-
opts = DriveOptions(
|
|
12
|
-
file_format="GeoTIFF",
|
|
13
|
-
drive_destination=DriveDestination(filename_prefix="out"),
|
|
14
|
-
)
|
|
15
|
-
assert opts.file_format == ImageFileFormat.GEO_TIFF
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_drive_options_accepts_canonical_value():
|
|
19
|
-
opts = DriveOptions(
|
|
20
|
-
file_format="GEO_TIFF",
|
|
21
|
-
drive_destination=DriveDestination(filename_prefix="out"),
|
|
22
|
-
)
|
|
23
|
-
assert opts.file_format == ImageFileFormat.GEO_TIFF
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def test_drive_options_rejects_unknown_string():
|
|
27
|
-
with pytest.raises(ValueError):
|
|
28
|
-
DriveOptions(
|
|
29
|
-
file_format="NotAFormat",
|
|
30
|
-
drive_destination=DriveDestination(filename_prefix="out"),
|
|
31
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|