earthscope-sdk 1.0.0b1__tar.gz → 1.2.0b0__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.
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/PKG-INFO +15 -4
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/README.md +10 -3
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/pyproject.toml +4 -2
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/__init__.py +1 -1
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/auth_flow.py +8 -6
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/client_credentials_flow.py +3 -13
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/device_code_flow.py +19 -10
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/_client.py +82 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_arrow/_common.py +94 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_arrow/_gnss.py +116 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_base.py +85 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_query_plan/_gnss_observations.py +295 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_query_plan/_query_plan.py +259 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_query_plan/_request_set.py +133 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_service.py +114 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/discovery/__init__.py +0 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/discovery/_base.py +303 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/discovery/_service.py +209 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/discovery/models.py +144 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/user/__init__.py +0 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/common/__init__.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/common/context.py +73 -1
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/common/service.py +10 -8
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/config/__init__.py +0 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/config/_bootstrap.py +42 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/config/models.py +54 -21
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/config/settings.py +11 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/model/secret.py +29 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/util/__init__.py +0 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/util/_concurrency.py +64 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/util/_itertools.py +57 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/util/_time.py +57 -0
- earthscope_sdk-1.2.0b0/src/earthscope_sdk/util/_types.py +5 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk.egg-info/PKG-INFO +15 -4
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk.egg-info/SOURCES.txt +25 -1
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk.egg-info/requires.txt +5 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/tests/test_auth.py +76 -2
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/tests/test_context.py +63 -3
- earthscope_sdk-1.2.0b0/tests/test_query_plan.py +212 -0
- earthscope_sdk-1.2.0b0/tests/test_request_set.py +120 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/tests/test_settings.py +212 -4
- earthscope_sdk-1.2.0b0/tests/test_utils.py +96 -0
- earthscope_sdk-1.0.0b1/src/earthscope_sdk/client/_client.py +0 -35
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/LICENSE +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/setup.cfg +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/setup.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/__init__.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/error.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/client/__init__.py +0 -0
- {earthscope_sdk-1.0.0b1/src/earthscope_sdk/client/user → earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access}/__init__.py +0 -0
- {earthscope_sdk-1.0.0b1/src/earthscope_sdk/common → earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_arrow}/__init__.py +0 -0
- {earthscope_sdk-1.0.0b1/src/earthscope_sdk/config → earthscope_sdk-1.2.0b0/src/earthscope_sdk/client/data_access/_query_plan}/__init__.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/client/user/_base.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/client/user/_service.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/client/user/models.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/common/_sync_runner.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/common/client.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/config/_compat.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/config/_util.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/config/error.py +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk.egg-info/dependency_links.txt +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk.egg-info/top_level.txt +0 -0
- {earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/tests/test_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: earthscope-sdk
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.0b0
|
4
4
|
Summary: An SDK for EarthScope API
|
5
5
|
Author-email: EarthScope <data-help@earthscope.org>
|
6
6
|
License: Apache License
|
@@ -216,12 +216,16 @@ License-File: LICENSE
|
|
216
216
|
Requires-Dist: httpx>=0.27.0
|
217
217
|
Requires-Dist: pydantic-settings[toml]>=2.8.0
|
218
218
|
Requires-Dist: stamina>=24.3.0
|
219
|
+
Provides-Extra: arrow
|
220
|
+
Requires-Dist: pyarrow>=20.0.0; extra == "arrow"
|
221
|
+
Requires-Dist: pyarrow-stubs>=20.0.0; extra == "arrow"
|
219
222
|
Provides-Extra: dev
|
220
223
|
Requires-Dist: bumpver; extra == "dev"
|
221
224
|
Requires-Dist: build; extra == "dev"
|
222
225
|
Requires-Dist: pytest; extra == "dev"
|
223
226
|
Requires-Dist: twine; extra == "dev"
|
224
227
|
Requires-Dist: pip-tools; extra == "dev"
|
228
|
+
Requires-Dist: pre-commit; extra == "dev"
|
225
229
|
Requires-Dist: pytest-httpx; extra == "dev"
|
226
230
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
227
231
|
Requires-Dist: ruff; extra == "dev"
|
@@ -241,9 +245,16 @@ Install from PyPI
|
|
241
245
|
pip install earthscope-sdk
|
242
246
|
```
|
243
247
|
|
248
|
+
Or with optional dependencies:
|
249
|
+
|
250
|
+
```shell
|
251
|
+
# install arrow dependencies for efficient data access
|
252
|
+
pip install earthscope-sdk[arrow]
|
253
|
+
```
|
254
|
+
|
244
255
|
### Usage
|
245
256
|
|
246
|
-
For detailed usage
|
257
|
+
For detailed usage info and examples, visit [our SDK docs](https://docs.earthscope.org/projects/SDK).
|
247
258
|
|
248
259
|
```py
|
249
260
|
# Import and create a client
|
@@ -300,7 +311,7 @@ Once refreshable credentials are available to the SDK, it will transparently han
|
|
300
311
|
|
301
312
|
### Same host
|
302
313
|
|
303
|
-
If you have the [EarthScope CLI](
|
314
|
+
If you have the [EarthScope CLI](https://docs.earthscope.org/projects/CLI) installed on the same host that is running your application which uses `earthscope-sdk`, you can simply log in using the CLI. The CLI shares credentials and configuration with this SDK (when running on the same host).
|
304
315
|
|
305
316
|
Running `es login` will open your browser and prompt you to log in to your EarthScope account.
|
306
317
|
|
@@ -320,7 +331,7 @@ Now when you run your application, `earthscope-sdk` will find your credentials.
|
|
320
331
|
|
321
332
|
Sometimes your workload runs on different hosts than your main workstation and you cannot feasibly "log in" on all of them. For example, maybe you're running many containers in your workload.
|
322
333
|
|
323
|
-
You can still use the [EarthScope CLI](
|
334
|
+
You can still use the [EarthScope CLI](https://docs.earthscope.org/projects/CLI) to facilitate auth for applications on other machines.
|
324
335
|
|
325
336
|
1. Use the CLI on your primary workstation [as described above](#same-host) to log in.
|
326
337
|
|
@@ -12,9 +12,16 @@ Install from PyPI
|
|
12
12
|
pip install earthscope-sdk
|
13
13
|
```
|
14
14
|
|
15
|
+
Or with optional dependencies:
|
16
|
+
|
17
|
+
```shell
|
18
|
+
# install arrow dependencies for efficient data access
|
19
|
+
pip install earthscope-sdk[arrow]
|
20
|
+
```
|
21
|
+
|
15
22
|
### Usage
|
16
23
|
|
17
|
-
For detailed usage
|
24
|
+
For detailed usage info and examples, visit [our SDK docs](https://docs.earthscope.org/projects/SDK).
|
18
25
|
|
19
26
|
```py
|
20
27
|
# Import and create a client
|
@@ -71,7 +78,7 @@ Once refreshable credentials are available to the SDK, it will transparently han
|
|
71
78
|
|
72
79
|
### Same host
|
73
80
|
|
74
|
-
If you have the [EarthScope CLI](
|
81
|
+
If you have the [EarthScope CLI](https://docs.earthscope.org/projects/CLI) installed on the same host that is running your application which uses `earthscope-sdk`, you can simply log in using the CLI. The CLI shares credentials and configuration with this SDK (when running on the same host).
|
75
82
|
|
76
83
|
Running `es login` will open your browser and prompt you to log in to your EarthScope account.
|
77
84
|
|
@@ -91,7 +98,7 @@ Now when you run your application, `earthscope-sdk` will find your credentials.
|
|
91
98
|
|
92
99
|
Sometimes your workload runs on different hosts than your main workstation and you cannot feasibly "log in" on all of them. For example, maybe you're running many containers in your workload.
|
93
100
|
|
94
|
-
You can still use the [EarthScope CLI](
|
101
|
+
You can still use the [EarthScope CLI](https://docs.earthscope.org/projects/CLI) to facilitate auth for applications on other machines.
|
95
102
|
|
96
103
|
1. Use the CLI on your primary workstation [as described above](#same-host) to log in.
|
97
104
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "earthscope-sdk"
|
7
|
-
version = "1.
|
7
|
+
version = "1.2.0b0"
|
8
8
|
description = "An SDK for EarthScope API"
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [{ name = "EarthScope", email = "data-help@earthscope.org" }]
|
@@ -25,12 +25,14 @@ dependencies = [
|
|
25
25
|
requires-python = ">=3.9"
|
26
26
|
|
27
27
|
[project.optional-dependencies]
|
28
|
+
arrow = ["pyarrow>=20.0.0", "pyarrow-stubs>=20.0.0"]
|
28
29
|
dev = [
|
29
30
|
"bumpver",
|
30
31
|
"build",
|
31
32
|
"pytest",
|
32
33
|
"twine",
|
33
34
|
"pip-tools",
|
35
|
+
"pre-commit",
|
34
36
|
"pytest-httpx",
|
35
37
|
"pytest-asyncio",
|
36
38
|
"ruff",
|
@@ -40,7 +42,7 @@ dev = [
|
|
40
42
|
Homepage = "https://gitlab.com/earthscope/public/earthscope-sdk"
|
41
43
|
|
42
44
|
[tool.bumpver]
|
43
|
-
current_version = "1.
|
45
|
+
current_version = "1.2.0b0"
|
44
46
|
version_pattern = "MAJOR.MINOR.PATCH[PYTAGNUM]"
|
45
47
|
commit_message = "chore: bump version {old_version} -> {new_version}"
|
46
48
|
commit = true
|
@@ -311,9 +311,10 @@ class AuthFlow(httpx.Auth):
|
|
311
311
|
"""
|
312
312
|
super().async_auth_flow
|
313
313
|
if request.headers.get("authorization") is None:
|
314
|
-
|
315
|
-
|
316
|
-
|
314
|
+
if self._settings.is_host_allowed(request.url.host):
|
315
|
+
await self.async_refresh_if_necessary()
|
316
|
+
access_token = self.access_token
|
317
|
+
request.headers["authorization"] = f"Bearer {access_token}"
|
317
318
|
|
318
319
|
yield request
|
319
320
|
|
@@ -326,8 +327,9 @@ class AuthFlow(httpx.Auth):
|
|
326
327
|
# NOTE: we explicitly redefine this sync method because ctx.syncify()
|
327
328
|
# does not support generators
|
328
329
|
if request.headers.get("authorization") is None:
|
329
|
-
self.
|
330
|
-
|
331
|
-
|
330
|
+
if self._settings.is_host_allowed(request.url.host):
|
331
|
+
self.refresh_if_necessary()
|
332
|
+
access_token = self.access_token
|
333
|
+
request.headers["authorization"] = f"Bearer {access_token}"
|
332
334
|
|
333
335
|
yield request
|
{earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/client_credentials_flow.py
RENAMED
@@ -1,5 +1,4 @@
|
|
1
1
|
import logging
|
2
|
-
from dataclasses import dataclass
|
3
2
|
from json import JSONDecodeError
|
4
3
|
|
5
4
|
from earthscope_sdk.auth.auth_flow import AuthFlow
|
@@ -9,12 +8,6 @@ from earthscope_sdk.common.context import SdkContext
|
|
9
8
|
logger = logging.getLogger(__name__)
|
10
9
|
|
11
10
|
|
12
|
-
@dataclass
|
13
|
-
class GetTokensErrorResponse:
|
14
|
-
error: str
|
15
|
-
error_description: str
|
16
|
-
|
17
|
-
|
18
11
|
class ClientCredentialsFlow(AuthFlow):
|
19
12
|
"""
|
20
13
|
Implements the oauth2 Client Credentials "machine-to-machine" (M2M) flow.
|
@@ -69,12 +62,9 @@ class ClientCredentialsFlow(AuthFlow):
|
|
69
62
|
|
70
63
|
# Unauthorized
|
71
64
|
if r.status_code == 401:
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
raise UnauthorizedError(
|
76
|
-
f"m2m client '{self._settings.client_id}' is not authorized"
|
77
|
-
)
|
65
|
+
raise UnauthorizedError(
|
66
|
+
f"m2m client '{self._settings.client_id}' is not authorized. IdP response: {resp}"
|
67
|
+
)
|
78
68
|
|
79
69
|
# Unhandled
|
80
70
|
raise ClientCredentialsFlowError("client credentials flow failed", r.content)
|
{earthscope_sdk-1.0.0b1 → earthscope_sdk-1.2.0b0}/src/earthscope_sdk/auth/device_code_flow.py
RENAMED
@@ -1,11 +1,12 @@
|
|
1
1
|
import logging
|
2
|
+
from asyncio import sleep
|
2
3
|
from contextlib import asynccontextmanager, contextmanager
|
3
|
-
from dataclasses import dataclass
|
4
4
|
from enum import Enum
|
5
5
|
from json import JSONDecodeError
|
6
|
-
from time import sleep
|
7
6
|
from typing import Optional
|
8
7
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
9
|
+
|
9
10
|
from earthscope_sdk.auth.auth_flow import AuthFlow
|
10
11
|
from earthscope_sdk.auth.error import (
|
11
12
|
DeviceCodePollingError,
|
@@ -25,18 +26,16 @@ class PollingErrorType(str, Enum):
|
|
25
26
|
ACCESS_DENIED = "access_denied"
|
26
27
|
|
27
28
|
|
28
|
-
|
29
|
-
class GetDeviceCodeResponse:
|
29
|
+
class GetDeviceCodeResponse(BaseModel):
|
30
30
|
device_code: str
|
31
31
|
user_code: str
|
32
32
|
verification_uri: str
|
33
33
|
verification_uri_complete: str
|
34
34
|
expires_in: int
|
35
|
-
interval:
|
35
|
+
interval: float
|
36
36
|
|
37
37
|
|
38
|
-
|
39
|
-
class PollingErrorResponse:
|
38
|
+
class PollingErrorResponse(BaseModel):
|
40
39
|
error: PollingErrorType
|
41
40
|
error_description: str
|
42
41
|
|
@@ -157,7 +156,7 @@ class DeviceCodeFlow(AuthFlow):
|
|
157
156
|
try:
|
158
157
|
while True:
|
159
158
|
# IdP-provided poll interval
|
160
|
-
sleep(codes.interval)
|
159
|
+
await sleep(codes.interval)
|
161
160
|
|
162
161
|
r = await self._ctx.httpx_client.post(
|
163
162
|
f"{self._settings.domain}oauth/token",
|
@@ -185,7 +184,12 @@ class DeviceCodeFlow(AuthFlow):
|
|
185
184
|
return self
|
186
185
|
|
187
186
|
# Keep polling
|
188
|
-
|
187
|
+
try:
|
188
|
+
poll_err = PollingErrorResponse.model_validate(resp)
|
189
|
+
except ValidationError as e:
|
190
|
+
raise DeviceCodePollingError(
|
191
|
+
f"Failed to unpack polling response: {r.text}"
|
192
|
+
) from e
|
189
193
|
if poll_err.error in [
|
190
194
|
PollingErrorType.AUTHORIZATION_PENDING,
|
191
195
|
PollingErrorType.SLOW_DOWN,
|
@@ -235,7 +239,12 @@ class DeviceCodeFlow(AuthFlow):
|
|
235
239
|
f"Failed to get a device code: {r.content}"
|
236
240
|
)
|
237
241
|
|
238
|
-
|
242
|
+
try:
|
243
|
+
codes = GetDeviceCodeResponse.model_validate_json(r.content)
|
244
|
+
except ValidationError as e:
|
245
|
+
raise DeviceCodeRequestDeviceCodeError(
|
246
|
+
f"Failed to unpack device code response: {r.text}"
|
247
|
+
) from e
|
239
248
|
|
240
249
|
logger.debug(f"Got device code response: {codes}")
|
241
250
|
return codes
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from functools import cached_property
|
2
|
+
|
3
|
+
from earthscope_sdk.common.client import AsyncSdkClient, SdkClient
|
4
|
+
|
5
|
+
|
6
|
+
class AsyncEarthScopeClient(AsyncSdkClient):
|
7
|
+
"""
|
8
|
+
An async client for interacting with api.earthscope.org
|
9
|
+
"""
|
10
|
+
|
11
|
+
@cached_property
|
12
|
+
def data(self):
|
13
|
+
"""
|
14
|
+
Data access functionality
|
15
|
+
"""
|
16
|
+
# lazy load
|
17
|
+
from earthscope_sdk.client.data_access._service import AsyncDataAccessService
|
18
|
+
|
19
|
+
return AsyncDataAccessService(self)
|
20
|
+
|
21
|
+
@cached_property
|
22
|
+
def discover(self):
|
23
|
+
"""
|
24
|
+
Data discovery functionality
|
25
|
+
"""
|
26
|
+
# lazy load
|
27
|
+
from earthscope_sdk.client.discovery._service import AsyncDiscoveryService
|
28
|
+
|
29
|
+
return AsyncDiscoveryService(self._ctx)
|
30
|
+
|
31
|
+
@cached_property
|
32
|
+
def user(self):
|
33
|
+
"""
|
34
|
+
User and Identity Management functionality
|
35
|
+
"""
|
36
|
+
# lazy load
|
37
|
+
from earthscope_sdk.client.user._service import AsyncUserService
|
38
|
+
|
39
|
+
return AsyncUserService(self._ctx)
|
40
|
+
|
41
|
+
|
42
|
+
class EarthScopeClient(SdkClient):
|
43
|
+
"""
|
44
|
+
A client for interacting with api.earthscope.org
|
45
|
+
"""
|
46
|
+
|
47
|
+
@cached_property
|
48
|
+
def _async_client(self):
|
49
|
+
"""
|
50
|
+
An async client for interacting with api.earthscope.org
|
51
|
+
"""
|
52
|
+
return AsyncEarthScopeClient(ctx=self._ctx)
|
53
|
+
|
54
|
+
@cached_property
|
55
|
+
def data(self):
|
56
|
+
"""
|
57
|
+
Data access functionality
|
58
|
+
"""
|
59
|
+
# lazy load
|
60
|
+
from earthscope_sdk.client.data_access._service import DataAccessService
|
61
|
+
|
62
|
+
return DataAccessService(self._async_client)
|
63
|
+
|
64
|
+
@cached_property
|
65
|
+
def discover(self):
|
66
|
+
"""
|
67
|
+
Data discovery functionality
|
68
|
+
"""
|
69
|
+
# lazy load
|
70
|
+
from earthscope_sdk.client.discovery._service import DiscoveryService
|
71
|
+
|
72
|
+
return DiscoveryService(self._ctx)
|
73
|
+
|
74
|
+
@cached_property
|
75
|
+
def user(self):
|
76
|
+
"""
|
77
|
+
User and Identity Management functionality
|
78
|
+
"""
|
79
|
+
# lazy load
|
80
|
+
from earthscope_sdk.client.user._service import UserService
|
81
|
+
|
82
|
+
return UserService(self._ctx)
|
@@ -0,0 +1,94 @@
|
|
1
|
+
try:
|
2
|
+
import pyarrow as pa
|
3
|
+
except ModuleNotFoundError as e:
|
4
|
+
raise ModuleNotFoundError(
|
5
|
+
"Optional dependency 'pyarrow' is required for this feature. "
|
6
|
+
"Install it with: pip install earthscope-sdk[arrow]"
|
7
|
+
) from e
|
8
|
+
|
9
|
+
|
10
|
+
from earthscope_sdk.client.discovery.models import DatasourceBaseModel
|
11
|
+
|
12
|
+
|
13
|
+
def convert_time(
|
14
|
+
table: pa.Table,
|
15
|
+
*,
|
16
|
+
source_field: str = "time",
|
17
|
+
target_field: str = "timestamp",
|
18
|
+
unit: str = "ms",
|
19
|
+
tz: str = "UTC",
|
20
|
+
) -> pa.Table:
|
21
|
+
"""
|
22
|
+
Convert a unix timestamp (integer since epoch) field to a timestamp type.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
table: The table to convert.
|
26
|
+
source_field: The name of the field to convert.
|
27
|
+
target_field: The name of the field to store the result.
|
28
|
+
unit: The unit of the time field.
|
29
|
+
tz: The timezone of the time field.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
The converted table.
|
33
|
+
"""
|
34
|
+
if unit not in {"s", "ms", "us", "ns"}:
|
35
|
+
raise ValueError(f"Invalid time unit: {unit}")
|
36
|
+
|
37
|
+
time_idx = table.schema.get_field_index(source_field)
|
38
|
+
if time_idx < 0:
|
39
|
+
return table
|
40
|
+
|
41
|
+
return table.set_column(
|
42
|
+
time_idx,
|
43
|
+
target_field,
|
44
|
+
table[source_field].cast(pa.timestamp(unit, tz)),
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
def get_datasource_metadata_table(
|
49
|
+
datasources: list[DatasourceBaseModel],
|
50
|
+
*,
|
51
|
+
fields: list[str],
|
52
|
+
namespaces: list[str],
|
53
|
+
) -> pa.Table:
|
54
|
+
"""
|
55
|
+
Get a pyarrow table containing metadata columns for a list of datasources.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
datasources: The list of datasources to get metadata for.
|
59
|
+
fields: The fields to include in the metadata table.
|
60
|
+
namespaces: The namespaces to include in the metadata table.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
The metadata table.
|
64
|
+
"""
|
65
|
+
return pa.Table.from_pylist(
|
66
|
+
[s.to_arrow_columns(fields=fields, namespaces=namespaces) for s in datasources]
|
67
|
+
)
|
68
|
+
|
69
|
+
|
70
|
+
def load_table_with_extra(content: bytes, *, is_stream=True, **kwargs) -> pa.Table:
|
71
|
+
"""
|
72
|
+
Load a table from an arrow stream or file. Optionally add extra metadata columns.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
content: The content of the stream or file.
|
76
|
+
is_stream: Whether the content is an arrow stream or an arrow file.
|
77
|
+
**kwargs: The extra metadata columns to add to the table.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
The loaded table.
|
81
|
+
"""
|
82
|
+
if is_stream:
|
83
|
+
reader = pa.ipc.open_stream(content)
|
84
|
+
else:
|
85
|
+
reader = pa.ipc.open_file(content)
|
86
|
+
|
87
|
+
table = reader.read_all()
|
88
|
+
|
89
|
+
if kwargs:
|
90
|
+
count = len(table)
|
91
|
+
for key, value in kwargs.items():
|
92
|
+
table = table.append_column(key, pa.repeat(value, count))
|
93
|
+
|
94
|
+
return table
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import pyarrow as pa
|
2
|
+
import pyarrow.compute as pc
|
3
|
+
|
4
|
+
from earthscope_sdk.client.data_access._arrow._common import convert_time
|
5
|
+
|
6
|
+
_SYSTEM_ID = "system_id"
|
7
|
+
_SYSTEM_TABLE = pa.Table.from_pylist(
|
8
|
+
[
|
9
|
+
{_SYSTEM_ID: 1, "system": "G"}, # GPS
|
10
|
+
{_SYSTEM_ID: 2, "system": "R"}, # GLONASS
|
11
|
+
{_SYSTEM_ID: 3, "system": "S"}, # SBAS
|
12
|
+
{_SYSTEM_ID: 4, "system": "E"}, # Galileo
|
13
|
+
{_SYSTEM_ID: 5, "system": "C"}, # BeiDou
|
14
|
+
{_SYSTEM_ID: 6, "system": "J"}, # QZSS
|
15
|
+
{_SYSTEM_ID: 7, "system": "I"}, # IRNSS/NavIC
|
16
|
+
],
|
17
|
+
schema=pa.schema(
|
18
|
+
[
|
19
|
+
pa.field(_SYSTEM_ID, pa.uint8()),
|
20
|
+
pa.field("system", pa.string()),
|
21
|
+
]
|
22
|
+
),
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
def _convert_obs_code(
|
27
|
+
table: pa.Table,
|
28
|
+
*,
|
29
|
+
source_field: str = "obs",
|
30
|
+
target_field: str = "obs_code",
|
31
|
+
) -> pa.Table:
|
32
|
+
"""
|
33
|
+
Convert the obs_code field from packed chars to a string.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
table: The table to convert.
|
37
|
+
source_field: The name of the field to convert.
|
38
|
+
target_field: The name of the field to store the result.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
The converted table.
|
42
|
+
"""
|
43
|
+
# extract obs_code (encoded as packed chars)
|
44
|
+
result_chunks = [
|
45
|
+
pc.cast(
|
46
|
+
pa.FixedSizeBinaryArray.from_buffers(
|
47
|
+
type=pa.binary(2),
|
48
|
+
length=len(chunk),
|
49
|
+
buffers=[None, chunk.buffers()[1]],
|
50
|
+
),
|
51
|
+
pa.string(),
|
52
|
+
)
|
53
|
+
for chunk in table[source_field].chunks
|
54
|
+
]
|
55
|
+
|
56
|
+
return table.set_column(
|
57
|
+
table.schema.get_field_index(source_field),
|
58
|
+
target_field,
|
59
|
+
# can't easily swap endianness of packed chars, so reverse the string
|
60
|
+
pc.utf8_reverse(pa.chunked_array(result_chunks, type=pa.string())),
|
61
|
+
)
|
62
|
+
|
63
|
+
|
64
|
+
def _convert_system(table: pa.Table, *, field: str) -> pa.Table:
|
65
|
+
"""
|
66
|
+
Convert the sys field from integer to string.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
table: The table to convert.
|
70
|
+
field: The name of the field to convert.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
The converted table.
|
74
|
+
"""
|
75
|
+
return table.join(_SYSTEM_TABLE, field, right_keys=_SYSTEM_ID).drop(field)
|
76
|
+
|
77
|
+
|
78
|
+
def prettify_observations_table(table: pa.Table) -> pa.Table:
|
79
|
+
"""
|
80
|
+
Clean up a raw observations table (as stored in TileDB) for easier use.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
table: The table to prettify.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
The prettified table.
|
87
|
+
"""
|
88
|
+
# Cast time column (integer milliseconds since epoch) to timestamp type
|
89
|
+
table = convert_time(table)
|
90
|
+
|
91
|
+
# Expose friendlier column names
|
92
|
+
table = table.rename_columns({"sat": "satellite"})
|
93
|
+
|
94
|
+
# Convert system column from integer to string
|
95
|
+
table = _convert_system(table, field="sys")
|
96
|
+
|
97
|
+
# Convert obs_code column from packed chars to string
|
98
|
+
table = _convert_obs_code(table)
|
99
|
+
|
100
|
+
return table
|
101
|
+
|
102
|
+
|
103
|
+
def prettify_positions_table(table: pa.Table) -> pa.Table:
|
104
|
+
"""
|
105
|
+
Clean up a raw positions table (as stored in TileDB) for easier use.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
table: The table to prettify.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
The prettified table.
|
112
|
+
"""
|
113
|
+
# Convert time column (integer milliseconds since epoch) to timestamp type
|
114
|
+
table = convert_time(table)
|
115
|
+
|
116
|
+
return table
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import datetime as dt
|
2
|
+
from typing import TYPE_CHECKING, Union
|
3
|
+
|
4
|
+
from earthscope_sdk.client.data_access._arrow._common import load_table_with_extra
|
5
|
+
from earthscope_sdk.common.service import SdkService
|
6
|
+
from earthscope_sdk.util._time import to_utc_dt
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from pyarrow import Table
|
10
|
+
|
11
|
+
|
12
|
+
class DataAccessBaseService(SdkService):
|
13
|
+
"""
|
14
|
+
L1 data access service functionality
|
15
|
+
"""
|
16
|
+
|
17
|
+
async def _get_gnss_observations(
|
18
|
+
self,
|
19
|
+
*,
|
20
|
+
session_edid: str,
|
21
|
+
start_datetime: dt.datetime,
|
22
|
+
end_datetime: dt.datetime,
|
23
|
+
system: Union[str, list[str]] = [],
|
24
|
+
satellite: Union[str, list[str]] = [],
|
25
|
+
obs_code: Union[str, list[str]] = [],
|
26
|
+
field: Union[str, list[str]] = [],
|
27
|
+
) -> "Table":
|
28
|
+
"""
|
29
|
+
Retrieve GNSS observations.
|
30
|
+
|
31
|
+
This method retrieves GNSS observations for a given session.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
session_edid: The session EDID to retrieve observations for.
|
35
|
+
start_datetime: The start datetime to retrieve positions for.
|
36
|
+
end_datetime: The end datetime to retrieve positions for.
|
37
|
+
system: The system(s) to retrieve observations for.
|
38
|
+
satellite: The satellite(s) to retrieve observations for.
|
39
|
+
obs_code: The observation code(s) to retrieve observations for.
|
40
|
+
field: The field(s) to retrieve observations for.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
A pyarrow table containing the GNSS observations.
|
44
|
+
"""
|
45
|
+
|
46
|
+
# lazy import
|
47
|
+
|
48
|
+
headers = {
|
49
|
+
"accept": "application/vnd.apache.arrow.stream",
|
50
|
+
}
|
51
|
+
|
52
|
+
params = {
|
53
|
+
"session_id": session_edid,
|
54
|
+
"start_datetime": to_utc_dt(start_datetime).isoformat(),
|
55
|
+
"end_datetime": to_utc_dt(end_datetime).isoformat(),
|
56
|
+
}
|
57
|
+
|
58
|
+
if system:
|
59
|
+
params["system"] = system
|
60
|
+
|
61
|
+
if satellite:
|
62
|
+
params["satellite"] = satellite
|
63
|
+
|
64
|
+
if obs_code:
|
65
|
+
params["obs_code"] = obs_code
|
66
|
+
|
67
|
+
if field:
|
68
|
+
params["field"] = field
|
69
|
+
|
70
|
+
req = self.ctx.httpx_client.build_request(
|
71
|
+
method="GET",
|
72
|
+
url=f"{self.resources.api_url}beta/data-products/gnss/observations",
|
73
|
+
headers=headers,
|
74
|
+
params=params,
|
75
|
+
timeout=30,
|
76
|
+
)
|
77
|
+
|
78
|
+
resp = await self._send_with_retries(req)
|
79
|
+
|
80
|
+
return await self.ctx.run_in_executor(
|
81
|
+
load_table_with_extra,
|
82
|
+
resp.content,
|
83
|
+
# Add edid column to the table returned by the API
|
84
|
+
edid=session_edid,
|
85
|
+
)
|