ee-client 1.2.2__tar.gz → 1.3.0__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-1.2.2 → ee_client-1.3.0}/PKG-INFO +6 -4
- {ee_client-1.2.2 → ee_client-1.3.0}/README.rst +3 -2
- {ee_client-1.2.2 → ee_client-1.3.0}/ee_client.egg-info/PKG-INFO +6 -4
- {ee_client-1.2.2 → ee_client-1.3.0}/ee_client.egg-info/SOURCES.txt +1 -1
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/__init__.py +1 -1
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/client.py +5 -7
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/data.py +1 -1
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/helpers.py +49 -1
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/logger.py +22 -3
- ee_client-1.2.2/eeclient/typing.py → ee_client-1.3.0/eeclient/models.py +4 -24
- {ee_client-1.2.2 → ee_client-1.3.0}/pyproject.toml +2 -2
- {ee_client-1.2.2 → ee_client-1.3.0}/tests/test_client.py +1 -1
- {ee_client-1.2.2 → ee_client-1.3.0}/tests/test_models.py +1 -8
- {ee_client-1.2.2 → ee_client-1.3.0}/LICENSE +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/ee_client.egg-info/dependency_links.txt +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/ee_client.egg-info/requires.txt +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/ee_client.egg-info/top_level.txt +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/exceptions.py +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/export/image.py +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/export/table.py +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/oauth_app.py +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/eeclient/tasks.py +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/setup.cfg +0 -0
- {ee_client-1.2.2 → ee_client-1.3.0}/tests/test_data.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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
|
|
@@ -27,6 +27,7 @@ Requires-Dist: tomli; extra == "dev"
|
|
|
27
27
|
Requires-Dist: mypy; extra == "dev"
|
|
28
28
|
Requires-Dist: pytest; extra == "dev"
|
|
29
29
|
Requires-Dist: flask; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
30
31
|
|
|
31
32
|
Earth Engine Client 🌎
|
|
32
33
|
==============================
|
|
@@ -103,12 +104,13 @@ After initializing the session, use the ``operations`` property to access the ke
|
|
|
103
104
|
|
|
104
105
|
import ee
|
|
105
106
|
|
|
106
|
-
# Initialize the Earth Engine library
|
|
107
|
+
# Initialize the Earth Engine library (this can use any authentication method/account)
|
|
108
|
+
# The purpose of this is to ensure the ee library is available for use
|
|
107
109
|
ee.Initialize()
|
|
108
110
|
|
|
109
111
|
# Use the operations available in the session
|
|
110
112
|
result_info = session.operations.get_info(ee.Number(5))
|
|
111
|
-
print(result_info)
|
|
113
|
+
print(result_info) # the GEE server call is done using the custom EE client
|
|
112
114
|
|
|
113
115
|
# Example: Generate a map ID for an Earth Engine image
|
|
114
116
|
image = ee.Image('COPERNICUS/S2/20190726T104031_20190726T104035_T31TGL')
|
|
@@ -73,12 +73,13 @@ After initializing the session, use the ``operations`` property to access the ke
|
|
|
73
73
|
|
|
74
74
|
import ee
|
|
75
75
|
|
|
76
|
-
# Initialize the Earth Engine library
|
|
76
|
+
# Initialize the Earth Engine library (this can use any authentication method/account)
|
|
77
|
+
# The purpose of this is to ensure the ee library is available for use
|
|
77
78
|
ee.Initialize()
|
|
78
79
|
|
|
79
80
|
# Use the operations available in the session
|
|
80
81
|
result_info = session.operations.get_info(ee.Number(5))
|
|
81
|
-
print(result_info)
|
|
82
|
+
print(result_info) # the GEE server call is done using the custom EE client
|
|
82
83
|
|
|
83
84
|
# Example: Generate a map ID for an Earth Engine image
|
|
84
85
|
image = ee.Image('COPERNICUS/S2/20190726T104031_20190726T104035_T31TGL')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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
|
|
@@ -27,6 +27,7 @@ Requires-Dist: tomli; extra == "dev"
|
|
|
27
27
|
Requires-Dist: mypy; extra == "dev"
|
|
28
28
|
Requires-Dist: pytest; extra == "dev"
|
|
29
29
|
Requires-Dist: flask; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
30
31
|
|
|
31
32
|
Earth Engine Client 🌎
|
|
32
33
|
==============================
|
|
@@ -103,12 +104,13 @@ After initializing the session, use the ``operations`` property to access the ke
|
|
|
103
104
|
|
|
104
105
|
import ee
|
|
105
106
|
|
|
106
|
-
# Initialize the Earth Engine library
|
|
107
|
+
# Initialize the Earth Engine library (this can use any authentication method/account)
|
|
108
|
+
# The purpose of this is to ensure the ee library is available for use
|
|
107
109
|
ee.Initialize()
|
|
108
110
|
|
|
109
111
|
# Use the operations available in the session
|
|
110
112
|
result_info = session.operations.get_info(ee.Number(5))
|
|
111
|
-
print(result_info)
|
|
113
|
+
print(result_info) # the GEE server call is done using the custom EE client
|
|
112
114
|
|
|
113
115
|
# Example: Generate a map ID for an Earth Engine image
|
|
114
116
|
image = ee.Image('COPERNICUS/S2/20190726T104031_20190726T104035_T31TGL')
|
|
@@ -12,9 +12,9 @@ eeclient/data.py
|
|
|
12
12
|
eeclient/exceptions.py
|
|
13
13
|
eeclient/helpers.py
|
|
14
14
|
eeclient/logger.py
|
|
15
|
+
eeclient/models.py
|
|
15
16
|
eeclient/oauth_app.py
|
|
16
17
|
eeclient/tasks.py
|
|
17
|
-
eeclient/typing.py
|
|
18
18
|
eeclient/export/image.py
|
|
19
19
|
eeclient/export/table.py
|
|
20
20
|
tests/test_client.py
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
1
|
from typing import Any, Dict, Literal, Optional
|
|
3
2
|
|
|
4
3
|
import os
|
|
5
4
|
import time
|
|
6
|
-
import json
|
|
7
5
|
import asyncio
|
|
8
6
|
import httpx
|
|
9
7
|
from contextlib import asynccontextmanager
|
|
10
8
|
|
|
11
|
-
|
|
12
9
|
from eeclient.export.image import image_to_asset, image_to_drive
|
|
13
10
|
from eeclient.logger import logger
|
|
14
11
|
from eeclient.exceptions import EEClientError, EERestException
|
|
15
12
|
from eeclient.tasks import get_task, get_task_by_name, get_tasks
|
|
16
|
-
from eeclient.
|
|
13
|
+
from eeclient.models import GEEHeaders, GoogleTokens, MapTileOptions, SepalHeaders
|
|
17
14
|
from eeclient.data import (
|
|
18
15
|
create_folder,
|
|
19
16
|
delete_asset,
|
|
@@ -49,7 +46,7 @@ class EESession:
|
|
|
49
46
|
self.max_retries = 3
|
|
50
47
|
|
|
51
48
|
self.enforce_project_id = enforce_project_id
|
|
52
|
-
|
|
49
|
+
logger.debug(str(sepal_headers))
|
|
53
50
|
self.sepal_headers = SepalHeaders.model_validate(sepal_headers)
|
|
54
51
|
self.sepal_session_id = self.sepal_headers.cookies["SEPAL-SESSIONID"]
|
|
55
52
|
self.sepal_user_data = self.sepal_headers.sepal_user
|
|
@@ -61,12 +58,13 @@ class EESession:
|
|
|
61
58
|
# if not I will get this error:
|
|
62
59
|
# httpx.HTTPStatusError: Client error '401 Unauthorized' for url 'https://danielg.sepal.io/api/user-files/listFiles/?path=%2F&extensions='
|
|
63
60
|
|
|
64
|
-
def _initialize_credentials(self):
|
|
61
|
+
def _initialize_credentials(self) -> None:
|
|
65
62
|
"""Initialize credentials from the initial Google tokens"""
|
|
66
63
|
_google_tokens = self.sepal_user_data.google_tokens
|
|
67
64
|
|
|
68
65
|
if not _google_tokens:
|
|
69
|
-
|
|
66
|
+
# Get them with the sepal_session_id
|
|
67
|
+
return asyncio.run(self.set_credentials())
|
|
70
68
|
|
|
71
69
|
self.expiry_date = _google_tokens.access_token_expiry_date
|
|
72
70
|
self.project_id = _google_tokens.project_id
|
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, List, Optional, Union
|
|
|
5
5
|
|
|
6
6
|
from eeclient.exceptions import EERestException
|
|
7
7
|
from eeclient.logger import logger
|
|
8
|
-
from eeclient.
|
|
8
|
+
from eeclient.models import MapTileOptions
|
|
9
9
|
from eeclient.helpers import _get_ee_image, convert_asset_id_to_asset_name
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import logging
|
|
4
|
+
from eeclient.models import SepalHeaders, SepalUser
|
|
5
|
+
from eeclient.logger import logger
|
|
1
6
|
from typing import Union
|
|
2
7
|
|
|
3
8
|
from ee.imagecollection import ImageCollection
|
|
4
9
|
from ee.feature import Feature
|
|
5
10
|
from ee.featurecollection import FeatureCollection
|
|
6
11
|
from ee.image import Image
|
|
7
|
-
from eeclient.
|
|
12
|
+
from eeclient.models import MapTileOptions
|
|
8
13
|
from ee.data import convert_asset_id_to_asset_name # type: ignore: it will be imported from another moduel
|
|
9
14
|
|
|
10
15
|
|
|
@@ -51,3 +56,46 @@ def parse_cookie_string(cookie_string):
|
|
|
51
56
|
key, value = key_value
|
|
52
57
|
cookies[key] = value
|
|
53
58
|
return cookies
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_sepal_headers_from_auth():
|
|
62
|
+
sepal_user = os.getenv("LOCAL_SEPAL_USER")
|
|
63
|
+
sepal_password = os.getenv("LOCAL_SEPAL_PASSWORD")
|
|
64
|
+
sepal_host = os.getenv("SEPAL_HOST")
|
|
65
|
+
|
|
66
|
+
if not sepal_user or not sepal_password or not sepal_host:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"LOCAL_SEPAL_USER, LOCAL_SEPAL_PASSWORD, and SEPAL_HOST must be set"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
session = requests.Session()
|
|
72
|
+
session.verify = False
|
|
73
|
+
|
|
74
|
+
creds_response = session.post(
|
|
75
|
+
f"https://{sepal_host}/api/user/login",
|
|
76
|
+
headers={
|
|
77
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
78
|
+
"No-Auth-Challenge": "true",
|
|
79
|
+
},
|
|
80
|
+
auth=(sepal_user, sepal_password),
|
|
81
|
+
data="",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
creds_response.raise_for_status()
|
|
85
|
+
|
|
86
|
+
logger.debug(f"Authentication successful. Cookies: {session.cookies}")
|
|
87
|
+
logger.debug(f"Response: {creds_response.json()}")
|
|
88
|
+
|
|
89
|
+
sepal_user_obj = SepalUser.model_validate(creds_response.json())
|
|
90
|
+
|
|
91
|
+
# Replace the project_id with the one from the environment
|
|
92
|
+
sepal_user_obj.google_tokens.project_id = "sepal-ui-421413"
|
|
93
|
+
|
|
94
|
+
cookies_dict = {cookie.name: cookie.value for cookie in session.cookies}
|
|
95
|
+
|
|
96
|
+
sepal_headers = {
|
|
97
|
+
"cookie": cookies_dict,
|
|
98
|
+
"sepal-user": sepal_user_obj,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return SepalHeaders.model_validate(sepal_headers)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import sys
|
|
2
3
|
import logging
|
|
3
4
|
from pathlib import Path
|
|
@@ -12,14 +13,32 @@ RESET = "\033[0m"
|
|
|
12
13
|
|
|
13
14
|
class CustomLogger:
|
|
14
15
|
def __init__(
|
|
15
|
-
self, name: str, level: int = logging.
|
|
16
|
+
self, name: str, level: int = logging.INFO, module_color: str = BLUE_BG
|
|
16
17
|
):
|
|
18
|
+
env_log_level_str = os.getenv(
|
|
19
|
+
"EE_CLIENT_LOG_LEVEL", logging.getLevelName(level)
|
|
20
|
+
).upper()
|
|
21
|
+
try:
|
|
22
|
+
effective_level = logging.getLevelName(env_log_level_str)
|
|
23
|
+
if not isinstance(effective_level, int):
|
|
24
|
+
effective_level = level
|
|
25
|
+
print(
|
|
26
|
+
f"Warning: Invalid EE_CLIENT_LOG_LEVEL '{env_log_level_str}'. Defaulting to {logging.getLevelName(level)}.",
|
|
27
|
+
file=sys.stderr,
|
|
28
|
+
)
|
|
29
|
+
except ValueError:
|
|
30
|
+
effective_level = level
|
|
31
|
+
print(
|
|
32
|
+
f"Warning: Could not parse EE_CLIENT_LOG_LEVEL '{env_log_level_str}'. Defaulting to {logging.getLevelName(level)}.",
|
|
33
|
+
file=sys.stderr,
|
|
34
|
+
)
|
|
35
|
+
|
|
17
36
|
self.logger = logging.getLogger(name)
|
|
18
|
-
self.logger.setLevel(
|
|
37
|
+
self.logger.setLevel(effective_level)
|
|
19
38
|
|
|
20
39
|
# Create a colored console handler.
|
|
21
40
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
22
|
-
console_handler.setLevel(level
|
|
41
|
+
console_handler.setLevel(effective_level) # Also set handler level
|
|
23
42
|
|
|
24
43
|
# Build the format string using the module_color for the logger's name.
|
|
25
44
|
format_str = f"%(log_color)s%(asctime)s - {module_color}%(name)s{RESET} - %(levelname)s - %(message)s"
|
|
@@ -69,38 +69,18 @@ class GoogleTokens(BaseModel):
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
class SepalUser(BaseModel):
|
|
72
|
-
id: int
|
|
73
72
|
username: str
|
|
74
|
-
|
|
73
|
+
id: Optional[int] = None
|
|
75
74
|
google_tokens: Optional[GoogleTokens] = None
|
|
76
|
-
status: str
|
|
77
|
-
roles: List[str]
|
|
78
|
-
system_user: bool
|
|
79
|
-
admin: bool
|
|
75
|
+
status: Optional[str] = None
|
|
76
|
+
roles: Optional[List[str]] = None
|
|
77
|
+
system_user: Optional[bool] = None
|
|
80
78
|
|
|
81
79
|
model_config = ConfigDict(
|
|
82
80
|
alias_generator=to_camel,
|
|
83
81
|
populate_by_name=True,
|
|
84
82
|
)
|
|
85
83
|
|
|
86
|
-
@model_validator(mode="before")
|
|
87
|
-
def check_google_tokens_present(cls, values: dict) -> dict:
|
|
88
|
-
# Look for the token in both the field name and its alias
|
|
89
|
-
token_value = values.get("google_tokens") or values.get("googleTokens")
|
|
90
|
-
if not token_value:
|
|
91
|
-
raise EEClientError(
|
|
92
|
-
"Authentication required: Please authenticate via sepal. See https://docs.sepal.io/en/latest/setup/gee.html."
|
|
93
|
-
)
|
|
94
|
-
return values
|
|
95
|
-
|
|
96
|
-
@model_validator(mode="after")
|
|
97
|
-
def check_google_tokens(self) -> "SepalUser":
|
|
98
|
-
if not self.google_tokens:
|
|
99
|
-
raise EEClientError(
|
|
100
|
-
"Authentication required: Please authenticate via sepal. See https://docs.sepal.io/en/latest/setup/gee.html."
|
|
101
|
-
)
|
|
102
|
-
return self
|
|
103
|
-
|
|
104
84
|
|
|
105
85
|
class SepalHeaders(BaseModel):
|
|
106
86
|
cookies: SepalCookies = Field(..., alias="cookie")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ee-client"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
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 = "1.
|
|
69
|
+
version = "1.3.0"
|
|
70
70
|
version_files = [
|
|
71
71
|
"pyproject.toml:version",
|
|
72
72
|
"eeclient/__init__.py:__version__",
|
|
@@ -16,7 +16,7 @@ def test_init_client(sepal_headers):
|
|
|
16
16
|
def test_is_expired(sepal_headers, dummy_headers):
|
|
17
17
|
|
|
18
18
|
sepal_session = EESession(sepal_headers=sepal_headers)
|
|
19
|
-
assert sepal_session.is_expired() ==
|
|
19
|
+
assert sepal_session.is_expired() == False
|
|
20
20
|
|
|
21
21
|
sepal_session = EESession(sepal_headers=dummy_headers)
|
|
22
22
|
assert sepal_session.is_expired() == True
|
|
@@ -4,7 +4,7 @@ import sys
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from eeclient.exceptions import EEClientError
|
|
7
|
-
from eeclient.
|
|
7
|
+
from eeclient.models import SepalHeaders
|
|
8
8
|
|
|
9
9
|
sys.path.append("..")
|
|
10
10
|
|
|
@@ -21,14 +21,7 @@ def test_headers(dummy_headers):
|
|
|
21
21
|
assert sepal_headers.sepal_user.google_tokens.project_id == "ee-project"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def test_headers_no_google_tokes(dummy_headers_no_google_tokens):
|
|
25
|
-
|
|
26
|
-
with pytest.raises(EEClientError):
|
|
27
|
-
SepalHeaders.model_validate(dummy_headers_no_google_tokens)
|
|
28
|
-
|
|
29
|
-
|
|
30
24
|
def test_without_sepal_user(dummy_headers_no_project_id):
|
|
31
25
|
|
|
32
26
|
with pytest.raises(EEClientError):
|
|
33
|
-
|
|
34
27
|
SepalHeaders.model_validate(dummy_headers_no_project_id)
|
|
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
|