ee-client 2.6.2__tar.gz → 3.0.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-2.6.2 → ee_client-3.0.0}/PKG-INFO +28 -30
- {ee_client-2.6.2 → ee_client-3.0.0}/README.rst +25 -29
- {ee_client-2.6.2 → ee_client-3.0.0}/ee_client.egg-info/PKG-INFO +28 -30
- {ee_client-2.6.2 → ee_client-3.0.0}/ee_client.egg-info/SOURCES.txt +8 -1
- {ee_client-2.6.2 → ee_client-3.0.0}/ee_client.egg-info/requires.txt +2 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/__init__.py +1 -1
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/client.py +127 -21
- ee_client-3.0.0/eeclient/credential_mixin.py +97 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/exceptions.py +7 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/helpers.py +21 -4
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/interfaces/export.py +4 -8
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/interfaces/operations.py +7 -14
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/interfaces/tasks.py +3 -6
- ee_client-3.0.0/eeclient/providers.py +419 -0
- ee_client-3.0.0/eeclient/sepal_credential_mixin.py +11 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/pyproject.toml +4 -2
- ee_client-3.0.0/tests/test_agnostic_auth_integration.py +38 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_cache.py +0 -1
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_export_table.py +4 -3
- ee_client-3.0.0/tests/test_factories.py +147 -0
- ee_client-3.0.0/tests/test_providers.py +184 -0
- ee_client-3.0.0/tests/test_resolution.py +94 -0
- ee_client-3.0.0/tests/test_tls_verify.py +51 -0
- ee_client-2.6.2/eeclient/sepal_credential_mixin.py +0 -384
- {ee_client-2.6.2 → ee_client-3.0.0}/LICENSE +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/ee_client.egg-info/dependency_links.txt +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/ee_client.egg-info/top_level.txt +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/cache.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/data.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/export/__init__.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/export/image.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/export/table.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/interfaces/__init__.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/models.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/oauth_app.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/eeclient/tasks.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/setup.cfg +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_client.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_data.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_export_image.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_integration_get_assets.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_models.py +0 -0
- {ee_client-2.6.2 → ee_client-3.0.0}/tests/test_tasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.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
|
|
@@ -18,6 +18,8 @@ Requires-Dist: earthengine-api
|
|
|
18
18
|
Requires-Dist: httpx[http2]
|
|
19
19
|
Requires-Dist: tenacity
|
|
20
20
|
Requires-Dist: pydantic
|
|
21
|
+
Requires-Dist: google-auth
|
|
22
|
+
Requires-Dist: requests
|
|
21
23
|
Provides-Extra: dev
|
|
22
24
|
Requires-Dist: pre-commit>=2.18.0; extra == "dev"
|
|
23
25
|
Requires-Dist: commitizen; extra == "dev"
|
|
@@ -60,13 +62,12 @@ While Google Earth Engine applications can be created using a global service acc
|
|
|
60
62
|
|
|
61
63
|
Unlike the standard GEE API—which relies on a global session object and does not support multi-user environments—this client ensures that each session is authenticated and managed independently with user-specific credentials.
|
|
62
64
|
|
|
63
|
-
Each session is instantiated via the ``EESession`` class,
|
|
65
|
+
Each session is instantiated via the ``EESession`` class, which supports several credential sources — a service-account key, an ``EARTHENGINE_TOKEN`` environment variable, ``earthengine authenticate`` OAuth credentials, or Application Default Credentials. Once authenticated, the session exposes an ``operations`` property that provides easy access to key API methods.
|
|
64
66
|
|
|
65
67
|
Key Features
|
|
66
68
|
------------
|
|
67
69
|
|
|
68
70
|
- **Custom User Authentication**: Enable users to access their private GEE assets without requiring them to be public, solving the limitation of global service account approaches.
|
|
69
|
-
- **SEPAL-based Initialization**: Create sessions using SEPAL headers. The required ``sepal-session-id`` cookie is automatically used to retrieve GEE credentials.
|
|
70
71
|
- **Multi-User Session Management**: Encapsulate user-specific credentials and project data in independent ``EESession`` objects.
|
|
71
72
|
- **Enhanced API Operations**: Access GEE functionalities via the ``operations`` property, which includes methods such as:
|
|
72
73
|
- ``get_info``: Retrieve detailed information about an Earth Engine object.
|
|
@@ -89,38 +90,35 @@ Usage
|
|
|
89
90
|
Initialization and Authentication
|
|
90
91
|
+++++++++++++++++++++++++++++++++
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
A session can be built from any standard Google/Earth Engine credential via factory constructors, each holding a live, refreshable credential:
|
|
93
94
|
|
|
94
95
|
.. code-block:: python
|
|
95
96
|
|
|
96
97
|
from eeclient import EESession
|
|
97
98
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Create and validate the session with SEPAL headers
|
|
123
|
-
session = EESession(sepal_headers)
|
|
99
|
+
# Service-account key (dict or path)
|
|
100
|
+
session = EESession.from_service_account("sa-key.json")
|
|
101
|
+
|
|
102
|
+
# EARTHENGINE_TOKEN env var, else ~/.config/earthengine/credentials
|
|
103
|
+
session = EESession.from_earthengine_token()
|
|
104
|
+
|
|
105
|
+
# An existing google.auth Credentials object
|
|
106
|
+
session = EESession.from_google_credentials(creds, project="my-project")
|
|
107
|
+
|
|
108
|
+
# Application Default Credentials (explicit opt-in)
|
|
109
|
+
session = EESession.from_application_default()
|
|
110
|
+
|
|
111
|
+
# Resolve from the environment (opt-in): EARTHENGINE_TOKEN / EE OAuth file
|
|
112
|
+
session = EESession.from_default()
|
|
113
|
+
|
|
114
|
+
Credentials are **never resolved implicitly** — a bare ``EESession()`` constructor call is not supported; always use a ``from_*`` factory to build a session. To resolve from the environment explicitly, call ``EESession.from_default()``, which walks local sources only, in this order:
|
|
115
|
+
|
|
116
|
+
1. ``EARTHENGINE_TOKEN`` environment variable
|
|
117
|
+
2. Earth Engine OAuth file (``~/.config/earthengine/credentials``)
|
|
118
|
+
|
|
119
|
+
Application Default Credentials are **not** included — call ``EESession.from_application_default()`` to use them.
|
|
120
|
+
|
|
121
|
+
To see which source a session ended up using, inspect ``session.auth_mode`` (the credential *kind*: ``file``/``oauth``/``service_account``/``adc``) and ``session.auth_source`` (the precise origin: ``earthengine_token``/``ee_oauth_file``/``service_account``/``application_default``/``google_credentials``).
|
|
124
122
|
|
|
125
123
|
Making API Calls
|
|
126
124
|
++++++++++++++++
|
|
@@ -29,13 +29,12 @@ While Google Earth Engine applications can be created using a global service acc
|
|
|
29
29
|
|
|
30
30
|
Unlike the standard GEE API—which relies on a global session object and does not support multi-user environments—this client ensures that each session is authenticated and managed independently with user-specific credentials.
|
|
31
31
|
|
|
32
|
-
Each session is instantiated via the ``EESession`` class,
|
|
32
|
+
Each session is instantiated via the ``EESession`` class, which supports several credential sources — a service-account key, an ``EARTHENGINE_TOKEN`` environment variable, ``earthengine authenticate`` OAuth credentials, or Application Default Credentials. Once authenticated, the session exposes an ``operations`` property that provides easy access to key API methods.
|
|
33
33
|
|
|
34
34
|
Key Features
|
|
35
35
|
------------
|
|
36
36
|
|
|
37
37
|
- **Custom User Authentication**: Enable users to access their private GEE assets without requiring them to be public, solving the limitation of global service account approaches.
|
|
38
|
-
- **SEPAL-based Initialization**: Create sessions using SEPAL headers. The required ``sepal-session-id`` cookie is automatically used to retrieve GEE credentials.
|
|
39
38
|
- **Multi-User Session Management**: Encapsulate user-specific credentials and project data in independent ``EESession`` objects.
|
|
40
39
|
- **Enhanced API Operations**: Access GEE functionalities via the ``operations`` property, which includes methods such as:
|
|
41
40
|
- ``get_info``: Retrieve detailed information about an Earth Engine object.
|
|
@@ -58,38 +57,35 @@ Usage
|
|
|
58
57
|
Initialization and Authentication
|
|
59
58
|
+++++++++++++++++++++++++++++++++
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
A session can be built from any standard Google/Earth Engine credential via factory constructors, each holding a live, refreshable credential:
|
|
62
61
|
|
|
63
62
|
.. code-block:: python
|
|
64
63
|
|
|
65
64
|
from eeclient import EESession
|
|
66
65
|
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Create and validate the session with SEPAL headers
|
|
92
|
-
session = EESession(sepal_headers)
|
|
66
|
+
# Service-account key (dict or path)
|
|
67
|
+
session = EESession.from_service_account("sa-key.json")
|
|
68
|
+
|
|
69
|
+
# EARTHENGINE_TOKEN env var, else ~/.config/earthengine/credentials
|
|
70
|
+
session = EESession.from_earthengine_token()
|
|
71
|
+
|
|
72
|
+
# An existing google.auth Credentials object
|
|
73
|
+
session = EESession.from_google_credentials(creds, project="my-project")
|
|
74
|
+
|
|
75
|
+
# Application Default Credentials (explicit opt-in)
|
|
76
|
+
session = EESession.from_application_default()
|
|
77
|
+
|
|
78
|
+
# Resolve from the environment (opt-in): EARTHENGINE_TOKEN / EE OAuth file
|
|
79
|
+
session = EESession.from_default()
|
|
80
|
+
|
|
81
|
+
Credentials are **never resolved implicitly** — a bare ``EESession()`` constructor call is not supported; always use a ``from_*`` factory to build a session. To resolve from the environment explicitly, call ``EESession.from_default()``, which walks local sources only, in this order:
|
|
82
|
+
|
|
83
|
+
1. ``EARTHENGINE_TOKEN`` environment variable
|
|
84
|
+
2. Earth Engine OAuth file (``~/.config/earthengine/credentials``)
|
|
85
|
+
|
|
86
|
+
Application Default Credentials are **not** included — call ``EESession.from_application_default()`` to use them.
|
|
87
|
+
|
|
88
|
+
To see which source a session ended up using, inspect ``session.auth_mode`` (the credential *kind*: ``file``/``oauth``/``service_account``/``adc``) and ``session.auth_source`` (the precise origin: ``earthengine_token``/``ee_oauth_file``/``service_account``/``application_default``/``google_credentials``).
|
|
93
89
|
|
|
94
90
|
Making API Calls
|
|
95
91
|
++++++++++++++++
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.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
|
|
@@ -18,6 +18,8 @@ Requires-Dist: earthengine-api
|
|
|
18
18
|
Requires-Dist: httpx[http2]
|
|
19
19
|
Requires-Dist: tenacity
|
|
20
20
|
Requires-Dist: pydantic
|
|
21
|
+
Requires-Dist: google-auth
|
|
22
|
+
Requires-Dist: requests
|
|
21
23
|
Provides-Extra: dev
|
|
22
24
|
Requires-Dist: pre-commit>=2.18.0; extra == "dev"
|
|
23
25
|
Requires-Dist: commitizen; extra == "dev"
|
|
@@ -60,13 +62,12 @@ While Google Earth Engine applications can be created using a global service acc
|
|
|
60
62
|
|
|
61
63
|
Unlike the standard GEE API—which relies on a global session object and does not support multi-user environments—this client ensures that each session is authenticated and managed independently with user-specific credentials.
|
|
62
64
|
|
|
63
|
-
Each session is instantiated via the ``EESession`` class,
|
|
65
|
+
Each session is instantiated via the ``EESession`` class, which supports several credential sources — a service-account key, an ``EARTHENGINE_TOKEN`` environment variable, ``earthengine authenticate`` OAuth credentials, or Application Default Credentials. Once authenticated, the session exposes an ``operations`` property that provides easy access to key API methods.
|
|
64
66
|
|
|
65
67
|
Key Features
|
|
66
68
|
------------
|
|
67
69
|
|
|
68
70
|
- **Custom User Authentication**: Enable users to access their private GEE assets without requiring them to be public, solving the limitation of global service account approaches.
|
|
69
|
-
- **SEPAL-based Initialization**: Create sessions using SEPAL headers. The required ``sepal-session-id`` cookie is automatically used to retrieve GEE credentials.
|
|
70
71
|
- **Multi-User Session Management**: Encapsulate user-specific credentials and project data in independent ``EESession`` objects.
|
|
71
72
|
- **Enhanced API Operations**: Access GEE functionalities via the ``operations`` property, which includes methods such as:
|
|
72
73
|
- ``get_info``: Retrieve detailed information about an Earth Engine object.
|
|
@@ -89,38 +90,35 @@ Usage
|
|
|
89
90
|
Initialization and Authentication
|
|
90
91
|
+++++++++++++++++++++++++++++++++
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
A session can be built from any standard Google/Earth Engine credential via factory constructors, each holding a live, refreshable credential:
|
|
93
94
|
|
|
94
95
|
.. code-block:: python
|
|
95
96
|
|
|
96
97
|
from eeclient import EESession
|
|
97
98
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Create and validate the session with SEPAL headers
|
|
123
|
-
session = EESession(sepal_headers)
|
|
99
|
+
# Service-account key (dict or path)
|
|
100
|
+
session = EESession.from_service_account("sa-key.json")
|
|
101
|
+
|
|
102
|
+
# EARTHENGINE_TOKEN env var, else ~/.config/earthengine/credentials
|
|
103
|
+
session = EESession.from_earthengine_token()
|
|
104
|
+
|
|
105
|
+
# An existing google.auth Credentials object
|
|
106
|
+
session = EESession.from_google_credentials(creds, project="my-project")
|
|
107
|
+
|
|
108
|
+
# Application Default Credentials (explicit opt-in)
|
|
109
|
+
session = EESession.from_application_default()
|
|
110
|
+
|
|
111
|
+
# Resolve from the environment (opt-in): EARTHENGINE_TOKEN / EE OAuth file
|
|
112
|
+
session = EESession.from_default()
|
|
113
|
+
|
|
114
|
+
Credentials are **never resolved implicitly** — a bare ``EESession()`` constructor call is not supported; always use a ``from_*`` factory to build a session. To resolve from the environment explicitly, call ``EESession.from_default()``, which walks local sources only, in this order:
|
|
115
|
+
|
|
116
|
+
1. ``EARTHENGINE_TOKEN`` environment variable
|
|
117
|
+
2. Earth Engine OAuth file (``~/.config/earthengine/credentials``)
|
|
118
|
+
|
|
119
|
+
Application Default Credentials are **not** included — call ``EESession.from_application_default()`` to use them.
|
|
120
|
+
|
|
121
|
+
To see which source a session ended up using, inspect ``session.auth_mode`` (the credential *kind*: ``file``/``oauth``/``service_account``/``adc``) and ``session.auth_source`` (the precise origin: ``earthengine_token``/``ee_oauth_file``/``service_account``/``application_default``/``google_credentials``).
|
|
124
122
|
|
|
125
123
|
Making API Calls
|
|
126
124
|
++++++++++++++++
|
|
@@ -9,11 +9,13 @@ ee_client.egg-info/top_level.txt
|
|
|
9
9
|
eeclient/__init__.py
|
|
10
10
|
eeclient/cache.py
|
|
11
11
|
eeclient/client.py
|
|
12
|
+
eeclient/credential_mixin.py
|
|
12
13
|
eeclient/data.py
|
|
13
14
|
eeclient/exceptions.py
|
|
14
15
|
eeclient/helpers.py
|
|
15
16
|
eeclient/models.py
|
|
16
17
|
eeclient/oauth_app.py
|
|
18
|
+
eeclient/providers.py
|
|
17
19
|
eeclient/sepal_credential_mixin.py
|
|
18
20
|
eeclient/tasks.py
|
|
19
21
|
eeclient/export/__init__.py
|
|
@@ -23,11 +25,16 @@ eeclient/interfaces/__init__.py
|
|
|
23
25
|
eeclient/interfaces/export.py
|
|
24
26
|
eeclient/interfaces/operations.py
|
|
25
27
|
eeclient/interfaces/tasks.py
|
|
28
|
+
tests/test_agnostic_auth_integration.py
|
|
26
29
|
tests/test_cache.py
|
|
27
30
|
tests/test_client.py
|
|
28
31
|
tests/test_data.py
|
|
29
32
|
tests/test_export_image.py
|
|
30
33
|
tests/test_export_table.py
|
|
34
|
+
tests/test_factories.py
|
|
31
35
|
tests/test_integration_get_assets.py
|
|
32
36
|
tests/test_models.py
|
|
33
|
-
tests/
|
|
37
|
+
tests/test_providers.py
|
|
38
|
+
tests/test_resolution.py
|
|
39
|
+
tests/test_tasks.py
|
|
40
|
+
tests/test_tls_verify.py
|
|
@@ -10,7 +10,7 @@ from contextlib import asynccontextmanager
|
|
|
10
10
|
|
|
11
11
|
from eeclient.exceptions import EEClientError, EERestException
|
|
12
12
|
from eeclient.models import GEEHeaders, SepalHeaders
|
|
13
|
-
from eeclient.
|
|
13
|
+
from eeclient.credential_mixin import CredentialMixin
|
|
14
14
|
from eeclient.cache import ResponseCache
|
|
15
15
|
|
|
16
16
|
import eeclient.export as _export_module
|
|
@@ -30,11 +30,6 @@ logger = logging.getLogger("eeclient")
|
|
|
30
30
|
# Default values that won't raise exceptions during import
|
|
31
31
|
EARTH_ENGINE_API_URL = "https://earthengine.googleapis.com/v1alpha"
|
|
32
32
|
|
|
33
|
-
# These will be set properly when EESession is initialized
|
|
34
|
-
SEPAL_HOST = os.getenv("SEPAL_HOST")
|
|
35
|
-
SEPAL_API_DOWNLOAD_URL = None
|
|
36
|
-
VERIFY_SSL = True
|
|
37
|
-
|
|
38
33
|
|
|
39
34
|
class SimpleRateLimiter:
|
|
40
35
|
def __init__(self, qps: float | None):
|
|
@@ -53,24 +48,29 @@ class SimpleRateLimiter:
|
|
|
53
48
|
self._next = max(now, self._next) + 1.0 / self.qps
|
|
54
49
|
|
|
55
50
|
|
|
56
|
-
class EESession(
|
|
51
|
+
class EESession(CredentialMixin):
|
|
57
52
|
def __init__(
|
|
58
53
|
self,
|
|
59
54
|
sepal_headers: Optional[SepalHeaders] = None,
|
|
60
55
|
enforce_project_id: bool = True,
|
|
56
|
+
*,
|
|
57
|
+
_provider=None,
|
|
61
58
|
):
|
|
62
|
-
"""Session
|
|
59
|
+
"""Session for the Earth Engine REST API with pluggable authentication.
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
Provide a credential source: ``sepal_headers`` for the SEPAL session
|
|
62
|
+
path, or use an ``EESession.from_*()`` factory / ``from_default()`` for
|
|
63
|
+
service-account, ``EARTHENGINE_TOKEN``, OAuth or ADC sources. A bare
|
|
64
|
+
``EESession()`` with no source raises ``EEClientError``.
|
|
66
65
|
|
|
67
66
|
Args:
|
|
68
|
-
sepal_headers (SepalHeaders):
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
sepal_headers (Optional[SepalHeaders]): SEPAL session headers; omit
|
|
68
|
+
and use a factory / ``from_default()`` for other sources.
|
|
69
|
+
enforce_project_id (bool, optional): If set, the project id is not
|
|
70
|
+
overwritten on refresh. Defaults to True.
|
|
71
71
|
|
|
72
72
|
Raises:
|
|
73
|
-
|
|
73
|
+
EEClientError: If no credential source is provided.
|
|
74
74
|
"""
|
|
75
75
|
self._inflight = asyncio.BoundedSemaphore(30)
|
|
76
76
|
self._rate = SimpleRateLimiter(60)
|
|
@@ -83,7 +83,7 @@ class EESession(SepalCredentialMixin):
|
|
|
83
83
|
self._assets_cache = ResponseCache(ttl=10.0, max_size=100)
|
|
84
84
|
|
|
85
85
|
self.enforce_project_id = enforce_project_id
|
|
86
|
-
super().__init__(sepal_headers)
|
|
86
|
+
super().__init__(sepal_headers, provider=_provider)
|
|
87
87
|
|
|
88
88
|
self.logger = logging.getLogger(f"eeclient.{self.user}")
|
|
89
89
|
self.logger.debug(
|
|
@@ -117,8 +117,9 @@ class EESession(SepalCredentialMixin):
|
|
|
117
117
|
"""Asynchronously create an EESession instance.
|
|
118
118
|
|
|
119
119
|
Args:
|
|
120
|
-
sepal_headers (Optional[SepalHeaders]):
|
|
121
|
-
|
|
120
|
+
sepal_headers (Optional[SepalHeaders]): SEPAL headers for SEPAL
|
|
121
|
+
session mode (required here). For other sources use an
|
|
122
|
+
EESession.from_*() factory, or EESession.from_default().
|
|
122
123
|
enforce_project_id (bool, optional): If set, it cannot be changed.
|
|
123
124
|
Defaults to True.
|
|
124
125
|
|
|
@@ -128,6 +129,113 @@ class EESession(SepalCredentialMixin):
|
|
|
128
129
|
session = cls(sepal_headers, enforce_project_id)
|
|
129
130
|
return await session.initialize()
|
|
130
131
|
|
|
132
|
+
# -- Agnostic credential factories -------------------------------------
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_google_credentials(
|
|
135
|
+
cls,
|
|
136
|
+
creds,
|
|
137
|
+
*,
|
|
138
|
+
project=None,
|
|
139
|
+
enforce_project_id=True,
|
|
140
|
+
_auth_mode="oauth",
|
|
141
|
+
_auth_source="google_credentials",
|
|
142
|
+
) -> "EESession":
|
|
143
|
+
"""Build a session from a live ``google.auth`` Credentials object."""
|
|
144
|
+
from eeclient.providers import GoogleAuthProvider
|
|
145
|
+
|
|
146
|
+
provider = GoogleAuthProvider(
|
|
147
|
+
creds, project, auth_mode=_auth_mode, auth_source=_auth_source
|
|
148
|
+
)
|
|
149
|
+
session = cls(enforce_project_id=enforce_project_id, _provider=provider)
|
|
150
|
+
session.set_credentials_sync() # eager load (factories are the sync path, D6)
|
|
151
|
+
return session
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_service_account(
|
|
155
|
+
cls, info_or_path, *, project=None, scopes=None, enforce_project_id=True
|
|
156
|
+
) -> "EESession":
|
|
157
|
+
"""Build a session from a service-account key (dict or file path)."""
|
|
158
|
+
from eeclient.providers import DEFAULT_SCOPES, _service_account_credentials
|
|
159
|
+
|
|
160
|
+
creds, sa_project = _service_account_credentials(
|
|
161
|
+
info_or_path, scopes or DEFAULT_SCOPES
|
|
162
|
+
)
|
|
163
|
+
return cls.from_google_credentials(
|
|
164
|
+
creds,
|
|
165
|
+
project=project or sa_project,
|
|
166
|
+
enforce_project_id=enforce_project_id,
|
|
167
|
+
_auth_mode="service_account",
|
|
168
|
+
_auth_source="service_account",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def from_earthengine_token(
|
|
173
|
+
cls, *, project=None, enforce_project_id=True
|
|
174
|
+
) -> "EESession":
|
|
175
|
+
"""Build a session from ``EARTHENGINE_TOKEN``, else the EE OAuth file."""
|
|
176
|
+
from eeclient.providers import (
|
|
177
|
+
DEFAULT_SCOPES,
|
|
178
|
+
_credentials_from_earthengine_token,
|
|
179
|
+
_google_auth_mode,
|
|
180
|
+
_oauth_credentials_from_ee_file,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
raw = os.getenv("EARTHENGINE_TOKEN")
|
|
184
|
+
if raw:
|
|
185
|
+
creds, tok_project = _credentials_from_earthengine_token(
|
|
186
|
+
raw, DEFAULT_SCOPES
|
|
187
|
+
)
|
|
188
|
+
mode, source = _google_auth_mode(creds), "earthengine_token"
|
|
189
|
+
else:
|
|
190
|
+
creds, tok_project = _oauth_credentials_from_ee_file(DEFAULT_SCOPES)
|
|
191
|
+
mode, source = "oauth", "ee_oauth_file"
|
|
192
|
+
return cls.from_google_credentials(
|
|
193
|
+
creds,
|
|
194
|
+
project=project or tok_project,
|
|
195
|
+
enforce_project_id=enforce_project_id,
|
|
196
|
+
_auth_mode=mode,
|
|
197
|
+
_auth_source=source,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def from_application_default(
|
|
202
|
+
cls, *, project=None, scopes=None, enforce_project_id=True
|
|
203
|
+
) -> "EESession":
|
|
204
|
+
"""Build a session from Application Default Credentials (opt-in ADC)."""
|
|
205
|
+
from eeclient.providers import DEFAULT_SCOPES, _application_default_credentials
|
|
206
|
+
|
|
207
|
+
creds, adc_project = _application_default_credentials(scopes or DEFAULT_SCOPES)
|
|
208
|
+
return cls.from_google_credentials(
|
|
209
|
+
creds,
|
|
210
|
+
project=project or adc_project,
|
|
211
|
+
enforce_project_id=enforce_project_id,
|
|
212
|
+
_auth_mode="adc",
|
|
213
|
+
_auth_source="application_default",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def from_sepal_headers(cls, headers, *, enforce_project_id=True) -> "EESession":
|
|
218
|
+
"""Build a session from SEPAL headers (names the existing path)."""
|
|
219
|
+
return cls(headers, enforce_project_id=enforce_project_id)
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def from_default(cls, *, enforce_project_id=True) -> "EESession":
|
|
223
|
+
"""Resolve credentials from the environment — explicit opt-in.
|
|
224
|
+
|
|
225
|
+
Walks local sources only (SEPAL file, ``EARTHENGINE_TOKEN``, Earth Engine
|
|
226
|
+
OAuth file) and fails closed in a SEPAL context. ADC is not included —
|
|
227
|
+
use ``from_application_default()``. This is opt-in: a bare ``EESession()``
|
|
228
|
+
does not auto-resolve; the caller must pick a source.
|
|
229
|
+
|
|
230
|
+
Construction does no network I/O — the token refresh is deferred to
|
|
231
|
+
``await initialize()`` / the first ``get_headers()`` so async callers are
|
|
232
|
+
not blocked (use ``set_credentials_sync()`` for a synchronous refresh).
|
|
233
|
+
"""
|
|
234
|
+
from eeclient.providers import resolve_default_provider
|
|
235
|
+
|
|
236
|
+
provider = resolve_default_provider()
|
|
237
|
+
return cls(enforce_project_id=enforce_project_id, _provider=provider)
|
|
238
|
+
|
|
131
239
|
async def get_assets_folder(self) -> str:
|
|
132
240
|
if self.needs_credentials_refresh():
|
|
133
241
|
await self.set_credentials()
|
|
@@ -149,7 +257,7 @@ class EESession(SepalCredentialMixin):
|
|
|
149
257
|
|
|
150
258
|
data = {
|
|
151
259
|
"x-goog-user-project": self.project_id,
|
|
152
|
-
"Authorization": f"Bearer {self.
|
|
260
|
+
"Authorization": f"Bearer {self.access_token}",
|
|
153
261
|
"Username": username,
|
|
154
262
|
}
|
|
155
263
|
|
|
@@ -268,9 +376,7 @@ class EESession(SepalCredentialMixin):
|
|
|
268
376
|
error_type = (
|
|
269
377
|
"Rate limit exceeded"
|
|
270
378
|
if e.code == 429
|
|
271
|
-
else "Unauthorized"
|
|
272
|
-
if e.code == 401
|
|
273
|
-
else "Service unavailable"
|
|
379
|
+
else "Unauthorized" if e.code == 401 else "Service unavailable"
|
|
274
380
|
)
|
|
275
381
|
attempt += 1
|
|
276
382
|
wait_time = min(initial_wait * (2**attempt), max_wait)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from eeclient.exceptions import EEClientError
|
|
6
|
+
from eeclient.models import SepalHeaders
|
|
7
|
+
from eeclient.providers import CredentialSnapshot, SepalSessionProvider
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("eeclient")
|
|
10
|
+
|
|
11
|
+
# SEPAL-specific attributes forwarded from a provider onto the session for
|
|
12
|
+
# backward compatibility (present on SepalSessionProvider / SepalFileProvider,
|
|
13
|
+
# absent -> None for google-auth providers).
|
|
14
|
+
_FORWARDED = (
|
|
15
|
+
"sepal_headers",
|
|
16
|
+
"sepal_user_data",
|
|
17
|
+
"sepal_host",
|
|
18
|
+
"sepal_session_id",
|
|
19
|
+
"sepal_api_download_url",
|
|
20
|
+
"credentials_path",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CredentialMixin:
|
|
25
|
+
"""Holds a :class:`CredentialProvider` and applies its snapshots.
|
|
26
|
+
|
|
27
|
+
The provider (SEPAL session, SEPAL file, or google-auth) owns the actual
|
|
28
|
+
credential logic; this mixin normalizes the result onto the session's
|
|
29
|
+
``access_token`` / ``project_id`` / ``expiry_date`` / ``_credentials``
|
|
30
|
+
surface and delegates refresh.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, sepal_headers: Optional[SepalHeaders] = None, *, provider=None):
|
|
34
|
+
self.max_retries = 3
|
|
35
|
+
self._credentials = None
|
|
36
|
+
self._service = None # backward compatibility
|
|
37
|
+
|
|
38
|
+
if provider is not None:
|
|
39
|
+
self._provider = provider
|
|
40
|
+
elif sepal_headers is not None:
|
|
41
|
+
self._provider = SepalSessionProvider(sepal_headers)
|
|
42
|
+
else:
|
|
43
|
+
raise EEClientError(
|
|
44
|
+
"EESession requires a credential source. Call "
|
|
45
|
+
"EESession.from_default() to resolve credentials from the "
|
|
46
|
+
"environment, or use an explicit EESession.from_*() factory."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
prov = self._provider
|
|
50
|
+
self.auth_mode = prov.auth_mode
|
|
51
|
+
self.auth_source = getattr(prov, "auth_source", None)
|
|
52
|
+
self.user = prov.user
|
|
53
|
+
self.verify_ssl = getattr(prov, "verify_ssl", True)
|
|
54
|
+
for name in _FORWARDED:
|
|
55
|
+
setattr(self, name, getattr(prov, name, None))
|
|
56
|
+
|
|
57
|
+
self.access_token = None
|
|
58
|
+
self.project_id = None
|
|
59
|
+
self.expiry_date = 0
|
|
60
|
+
|
|
61
|
+
snap = prov.initial_snapshot()
|
|
62
|
+
if snap is not None:
|
|
63
|
+
self._apply_snapshot(snap, initial=True)
|
|
64
|
+
|
|
65
|
+
self.logger = logging.getLogger(f"eeclient.{self.user}")
|
|
66
|
+
|
|
67
|
+
def _apply_snapshot(
|
|
68
|
+
self, snap: CredentialSnapshot, *, initial: bool = False
|
|
69
|
+
) -> None:
|
|
70
|
+
self.access_token = snap.access_token
|
|
71
|
+
self.expiry_date = snap.expiry_date
|
|
72
|
+
self._credentials = snap.native
|
|
73
|
+
# Preserve enforce_project_id: on refresh, don't overwrite an enforced
|
|
74
|
+
# project; on initial population always set it.
|
|
75
|
+
enforce = getattr(self, "enforce_project_id", False)
|
|
76
|
+
if initial or not (enforce and self.project_id):
|
|
77
|
+
self.project_id = snap.project_id
|
|
78
|
+
|
|
79
|
+
def is_expired(self) -> bool:
|
|
80
|
+
"""Returns if a token is about to expire."""
|
|
81
|
+
return (self.expiry_date / 1000) - time.time() < 60
|
|
82
|
+
|
|
83
|
+
def needs_credentials_refresh(self) -> bool:
|
|
84
|
+
"""Returns if credentials need to be refreshed (missing or expired)."""
|
|
85
|
+
return not self._credentials or self.is_expired()
|
|
86
|
+
|
|
87
|
+
async def set_credentials(self) -> None:
|
|
88
|
+
"""Refresh credentials via the active provider (async)."""
|
|
89
|
+
self._apply_snapshot(await self._provider.refresh())
|
|
90
|
+
|
|
91
|
+
def set_credentials_sync(self) -> None:
|
|
92
|
+
"""Refresh credentials via the active provider (sync)."""
|
|
93
|
+
self._apply_snapshot(self._provider.refresh_sync())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Backward-compat alias: SEPAL is now one provider among several.
|
|
97
|
+
SepalCredentialMixin = CredentialMixin
|
|
@@ -92,6 +92,13 @@ class SepalCredentialsUnavailableError(EEClientError):
|
|
|
92
92
|
super().__init__(error)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
class CredentialsResolutionError(EEClientError):
|
|
96
|
+
"""Raised when no credential source resolves for a default EESession()."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, message: str):
|
|
99
|
+
super().__init__(message)
|
|
100
|
+
|
|
101
|
+
|
|
95
102
|
# {'code': 401, 'message': 'Request had invalid authentication credentials.
|
|
96
103
|
# Expected OAuth 2 access token, login cookie or other valid authentication
|
|
97
104
|
# credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
|