ee-client 2.6.1__tar.gz → 2.6.3__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.
Files changed (36) hide show
  1. {ee_client-2.6.1 → ee_client-2.6.3}/PKG-INFO +1 -1
  2. {ee_client-2.6.1 → ee_client-2.6.3}/ee_client.egg-info/PKG-INFO +1 -1
  3. {ee_client-2.6.1 → ee_client-2.6.3}/ee_client.egg-info/SOURCES.txt +3 -1
  4. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/__init__.py +1 -1
  5. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/helpers.py +21 -4
  6. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/sepal_credential_mixin.py +2 -4
  7. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/tasks.py +6 -3
  8. {ee_client-2.6.1 → ee_client-2.6.3}/pyproject.toml +2 -2
  9. ee_client-2.6.3/tests/test_tasks.py +60 -0
  10. ee_client-2.6.3/tests/test_tls_verify.py +51 -0
  11. {ee_client-2.6.1 → ee_client-2.6.3}/LICENSE +0 -0
  12. {ee_client-2.6.1 → ee_client-2.6.3}/README.rst +0 -0
  13. {ee_client-2.6.1 → ee_client-2.6.3}/ee_client.egg-info/dependency_links.txt +0 -0
  14. {ee_client-2.6.1 → ee_client-2.6.3}/ee_client.egg-info/requires.txt +0 -0
  15. {ee_client-2.6.1 → ee_client-2.6.3}/ee_client.egg-info/top_level.txt +0 -0
  16. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/cache.py +0 -0
  17. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/client.py +0 -0
  18. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/data.py +0 -0
  19. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/exceptions.py +0 -0
  20. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/export/__init__.py +0 -0
  21. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/export/image.py +0 -0
  22. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/export/table.py +0 -0
  23. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/interfaces/__init__.py +0 -0
  24. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/interfaces/export.py +0 -0
  25. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/interfaces/operations.py +0 -0
  26. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/interfaces/tasks.py +0 -0
  27. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/models.py +0 -0
  28. {ee_client-2.6.1 → ee_client-2.6.3}/eeclient/oauth_app.py +0 -0
  29. {ee_client-2.6.1 → ee_client-2.6.3}/setup.cfg +0 -0
  30. {ee_client-2.6.1 → ee_client-2.6.3}/tests/test_cache.py +0 -0
  31. {ee_client-2.6.1 → ee_client-2.6.3}/tests/test_client.py +0 -0
  32. {ee_client-2.6.1 → ee_client-2.6.3}/tests/test_data.py +0 -0
  33. {ee_client-2.6.1 → ee_client-2.6.3}/tests/test_export_image.py +0 -0
  34. {ee_client-2.6.1 → ee_client-2.6.3}/tests/test_export_table.py +0 -0
  35. {ee_client-2.6.1 → ee_client-2.6.3}/tests/test_integration_get_assets.py +0 -0
  36. {ee_client-2.6.1 → ee_client-2.6.3}/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.1
3
+ Version: 2.6.3
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.1
3
+ Version: 2.6.3
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
@@ -29,4 +29,6 @@ tests/test_data.py
29
29
  tests/test_export_image.py
30
30
  tests/test_export_table.py
31
31
  tests/test_integration_get_assets.py
32
- tests/test_models.py
32
+ tests/test_models.py
33
+ tests/test_tasks.py
34
+ tests/test_tls_verify.py
@@ -1,6 +1,6 @@
1
1
  __title__ = "eeclient"
2
2
  __summary__ = "A client for Google Earth Engine"
3
- __version__ = "2.6.1"
3
+ __version__ = "2.6.3"
4
4
 
5
5
  __author__ = "Daniel Guerrero"
6
6
  __email__ = "dfgm2006@gmail.com"
@@ -13,6 +13,25 @@ from ee.data import convert_asset_id_to_asset_name # noqa: F401
13
13
 
14
14
  log = logging.getLogger("eeclient")
15
15
 
16
+ # Hosts that legitimately serve self-signed certs in local development.
17
+ # TLS verification is skipped for these (or via EECLIENT_INSECURE_TLS) only —
18
+ # never for arbitrary or production hosts.
19
+ _LOCAL_INSECURE_HOSTS = {"host.docker.internal"}
20
+
21
+
22
+ def should_verify_tls(host: Optional[str] = None) -> bool:
23
+ """Return whether TLS certificates should be verified for ``host``.
24
+
25
+ Secure by default. Verification is skipped only for known local-dev hosts
26
+ that serve self-signed certs, or when the ``EECLIENT_INSECURE_TLS`` env var
27
+ is explicitly truthy. It is never disabled for arbitrary/production hosts.
28
+ """
29
+ if os.getenv("EECLIENT_INSECURE_TLS", "").strip().lower() in {"1", "true", "yes"}:
30
+ return False
31
+ if host and host in _LOCAL_INSECURE_HOSTS:
32
+ return False
33
+ return True
34
+
16
35
 
17
36
  def _get_ee_image(
18
37
  ee_object: Union[Image, ImageCollection, Feature, FeatureCollection],
@@ -77,7 +96,7 @@ def get_sepal_headers_from_auth(
77
96
  )
78
97
 
79
98
  session = requests.Session()
80
- session.verify = False
99
+ session.verify = should_verify_tls(sepal_host)
81
100
 
82
101
  creds_response = session.post(
83
102
  f"https://{sepal_host}/api/user/login",
@@ -91,10 +110,8 @@ def get_sepal_headers_from_auth(
91
110
 
92
111
  creds_response.raise_for_status()
93
112
 
94
- log.debug(f"Authentication successful. Cookies: {session.cookies}")
95
- log.debug(f"Response>>>>>>>>>>: {creds_response.json()}")
96
-
97
113
  sepal_user_obj = SepalUser.model_validate(creds_response.json())
114
+ log.debug("Authentication successful for user '%s'", sepal_user_obj.username)
98
115
 
99
116
  cookies_dict = {cookie.name: cookie.value for cookie in session.cookies}
100
117
 
@@ -4,6 +4,7 @@ from eeclient.exceptions import (
4
4
  CredentialsFileUnknownError,
5
5
  SepalCredentialsUnavailableError,
6
6
  )
7
+ from eeclient.helpers import should_verify_tls
7
8
  import os
8
9
  import logging
9
10
  import json
@@ -50,10 +51,7 @@ class SepalCredentialMixin:
50
51
  f"https://{self.sepal_host}/api/user-files/download/"
51
52
  "?path=%2F.config%2Fearthengine%2Fcredentials"
52
53
  )
53
- self.verify_ssl = not (
54
- self.sepal_host == "host.docker.internal"
55
- or self.sepal_host == "danielg.sepal.io"
56
- )
54
+ self.verify_ssl = should_verify_tls(self.sepal_host)
57
55
 
58
56
  self._google_tokens = self.sepal_user_data.google_tokens
59
57
  if self._google_tokens:
@@ -24,9 +24,12 @@ class CamelCaseModel(BaseModel):
24
24
 
25
25
  class Stage(CamelCaseModel):
26
26
  display_name: str
27
- complete_work_units: int
28
- total_work_units: str
29
- description: str
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.1"
7
+ version = "2.6.3"
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.1"
69
+ version = "2.6.3"
70
70
  version_files = [
71
71
  "pyproject.toml:version",
72
72
  "eeclient/__init__.py:__version__",
@@ -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
@@ -0,0 +1,51 @@
1
+ """TLS verification policy for ee-client.
2
+
3
+ Security regression guard: TLS must be verified by default for every host,
4
+ including the maintainer's former personal dev host. Verification may only be
5
+ skipped for known local-dev hosts (self-signed) or via an explicit opt-in env
6
+ var — never for arbitrary or production hosts.
7
+ """
8
+
9
+ import pytest
10
+
11
+ from eeclient.helpers import should_verify_tls
12
+
13
+ INSECURE_ENV = "EECLIENT_INSECURE_TLS"
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def _clear_insecure_env(monkeypatch):
18
+ monkeypatch.delenv(INSECURE_ENV, raising=False)
19
+
20
+
21
+ @pytest.mark.parametrize(
22
+ "host",
23
+ [
24
+ "sepal.io",
25
+ "danielg.sepal.io", # former hardcoded insecure host — must now verify
26
+ "danielg.sepal.io.attacker.com", # must not match via substring
27
+ "anything.example.com",
28
+ None, # file-based auth (no host)
29
+ ],
30
+ )
31
+ def test_verifies_by_default(host):
32
+ assert should_verify_tls(host) is True
33
+
34
+
35
+ def test_local_docker_host_is_skipped():
36
+ # Local Docker host serves a self-signed cert and cannot be meaningfully
37
+ # MITM'd; skipping verification there is the one defensible exception.
38
+ assert should_verify_tls("host.docker.internal") is False
39
+
40
+
41
+ @pytest.mark.parametrize("value", ["1", "true", "TRUE", "yes", "Yes"])
42
+ def test_explicit_opt_in_disables_verification(monkeypatch, value):
43
+ monkeypatch.setenv(INSECURE_ENV, value)
44
+ assert should_verify_tls("danielg.sepal.io") is False
45
+ assert should_verify_tls("sepal.io") is False
46
+
47
+
48
+ @pytest.mark.parametrize("value", ["", "0", "false", "no", "off"])
49
+ def test_non_truthy_opt_in_keeps_verification(monkeypatch, value):
50
+ monkeypatch.setenv(INSECURE_ENV, value)
51
+ assert should_verify_tls("sepal.io") is True
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