earthscope-sdk 1.0.0b0__py3-none-any.whl → 1.1.0__py3-none-any.whl

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.
@@ -1,4 +1,4 @@
1
- __version__ = "1.0.0b0"
1
+ __version__ = "1.1.0"
2
2
 
3
3
  from earthscope_sdk.client import AsyncEarthScopeClient, EarthScopeClient
4
4
 
@@ -157,12 +157,14 @@ class AuthFlow(httpx.Auth):
157
157
  NoRefreshTokenError: no refresh token is present
158
158
  InvalidRefreshTokenError: the token refresh failed
159
159
  """
160
+ from httpx import HTTPStatusError, ReadTimeout
161
+
160
162
  refresh_token = self.refresh_token
161
163
  scope = scope or self._settings.scope
162
164
 
163
- r = await self._ctx.httpx_client.post(
165
+ request = self._ctx.httpx_client.build_request(
166
+ "POST",
164
167
  f"{self._settings.domain}oauth/token",
165
- auth=None, # override client default
166
168
  headers={"content-type": "application/x-www-form-urlencoded"},
167
169
  data={
168
170
  "grant_type": "refresh_token",
@@ -171,9 +173,22 @@ class AuthFlow(httpx.Auth):
171
173
  "scopes": scope,
172
174
  },
173
175
  )
174
- if r.status_code != 200:
175
- logger.error(f"error during token refresh: {r.content}")
176
+
177
+ try:
178
+ async for attempt in self._settings.retry.retry_context(ReadTimeout):
179
+ with attempt:
180
+ r = await self._ctx.httpx_client.send(request, auth=None)
181
+ r.raise_for_status()
182
+ except HTTPStatusError as e:
183
+ logger.error(
184
+ f"error during token refresh ({attempt.num} attempts): {e.response.content}"
185
+ )
176
186
  raise InvalidRefreshTokenError("refresh token exchange failed")
187
+ except Exception as e:
188
+ logger.error(
189
+ f"error during token refresh ({attempt.num} attempts)", exc_info=e
190
+ )
191
+ raise InvalidRefreshTokenError("refresh token exchange failed") from e
177
192
 
178
193
  # add previous refresh token to new tokens if omitted from resp
179
194
  # (i.e. we have a non-rotating refresh token)
@@ -296,9 +311,10 @@ class AuthFlow(httpx.Auth):
296
311
  """
297
312
  super().async_auth_flow
298
313
  if request.headers.get("authorization") is None:
299
- await self.async_refresh_if_necessary()
300
- access_token = self.access_token
301
- request.headers["authorization"] = f"Bearer {access_token}"
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}"
302
318
 
303
319
  yield request
304
320
 
@@ -311,8 +327,9 @@ class AuthFlow(httpx.Auth):
311
327
  # NOTE: we explicitly redefine this sync method because ctx.syncify()
312
328
  # does not support generators
313
329
  if request.headers.get("authorization") is None:
314
- self.refresh_if_necessary()
315
- access_token = self.access_token
316
- request.headers["authorization"] = f"Bearer {access_token}"
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}"
317
334
 
318
335
  yield request
@@ -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
- err = GetTokensErrorResponse(**resp)
73
- if err.error == "access_denied":
74
- if err.error_description == "Unauthorized":
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)
@@ -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
- @dataclass
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: int
35
+ interval: float
36
36
 
37
37
 
38
- @dataclass
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
- poll_err = PollingErrorResponse(**resp)
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
- codes = GetDeviceCodeResponse(**r.json())
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
@@ -18,7 +18,7 @@ class UserBaseService(SdkService):
18
18
  url=f"{self.resources.api_url}beta/user/credentials/aws/{role}",
19
19
  )
20
20
 
21
- resp = await self._send(req)
21
+ resp = await self._send_with_retries(req)
22
22
 
23
23
  return AwsTemporaryCredentials.model_validate_json(resp.content)
24
24
 
@@ -34,6 +34,6 @@ class UserBaseService(SdkService):
34
34
  url=f"{self.resources.api_url}beta/user/profile",
35
35
  )
36
36
 
37
- resp = await self._send(req)
37
+ resp = await self._send_with_retries(req)
38
38
 
39
39
  return UserProfile.model_validate_json(resp.content)
@@ -78,6 +78,8 @@ class SdkContext:
78
78
  self._httpx_client = httpx.AsyncClient(
79
79
  auth=self.auth_flow,
80
80
  headers={
81
+ **self.settings.http.extra_headers,
82
+ # override anything specified via extra_headers
81
83
  "user-agent": self.settings.http.user_agent,
82
84
  },
83
85
  limits=self.settings.http.limits,
@@ -52,3 +52,8 @@ class SdkService:
52
52
  resp.raise_for_status()
53
53
 
54
54
  return resp
55
+
56
+ async def _send_with_retries(self, request: "Request"):
57
+ async for attempt in self.ctx.settings.http.retry.retry_context():
58
+ with attempt:
59
+ return await self._send(request=request)
@@ -0,0 +1,42 @@
1
+ """
2
+ This module facilitates bootstrapping SDK settings from a JSON-encoded environment variable.
3
+ """
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+
9
+ from pydantic_settings import PydanticBaseSettingsSource
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BootstrapEnvironmentSettingsSource(PydanticBaseSettingsSource):
15
+ """
16
+ This SettingsSource facilitates bootstrapping the SDK from a special environment variable.
17
+
18
+ The environment variable should be a JSON string of the expected SDK settings and structure.
19
+ """
20
+
21
+ def __init__(self, settings_cls, env_var: str):
22
+ super().__init__(settings_cls)
23
+ self._env_var = env_var
24
+
25
+ def __call__(self):
26
+ try:
27
+ bootstrap_settings = os.environ[self._env_var]
28
+ except KeyError:
29
+ return {}
30
+
31
+ try:
32
+ return json.loads(bootstrap_settings)
33
+ except json.JSONDecodeError:
34
+ logger.warning(
35
+ f"Found bootstrap environment variable '{self._env_var}', but unable to decode content as JSON"
36
+ )
37
+ return {}
38
+
39
+ def __repr__(self) -> str:
40
+ return f"{self.__class__.__name__}(env_var='{self._env_var}')"
41
+
42
+ def get_field_value(self, *args, **kwargs): ... # unused abstract method
@@ -1,11 +1,14 @@
1
1
  import base64
2
2
  import binascii
3
3
  import datetime as dt
4
+ import fnmatch
5
+ import functools
4
6
  from contextlib import suppress
5
7
  from enum import Enum
6
8
  from functools import cached_property
7
- from typing import Annotated, Any, Optional, Union
9
+ from typing import Annotated, Any, Optional, Type, Union
8
10
 
11
+ from annotated_types import Ge, Gt
9
12
  from pydantic import (
10
13
  AliasChoices,
11
14
  BaseModel,
@@ -13,14 +16,12 @@ from pydantic import (
13
16
  ConfigDict,
14
17
  Field,
15
18
  HttpUrl,
16
- SecretStr,
17
- SerializationInfo,
18
19
  ValidationError,
19
- field_serializer,
20
20
  model_validator,
21
21
  )
22
22
 
23
23
  from earthscope_sdk import __version__
24
+ from earthscope_sdk.model.secret import SecretStr
24
25
 
25
26
 
26
27
  def _try_float(v: Any):
@@ -86,29 +87,12 @@ class Tokens(BaseModel):
86
87
  return None
87
88
 
88
89
  with suppress(IndexError, binascii.Error, ValidationError):
89
- payload_b64 = self.access_token.get_secret_value().split(".")[1]
90
+ payload_b64 = self.access_token.get_secret_value().split(".", 2)[1]
90
91
  payload = base64.b64decode(payload_b64 + "==") # extra padding
91
92
  return AccessTokenBody.model_validate_json(payload)
92
93
 
93
94
  raise ValueError("Unable to decode access token body")
94
95
 
95
- @field_serializer("access_token", "id_token", "refresh_token", when_used="json")
96
- def dump_secret_json(self, secret: Optional[SecretStr], info: SerializationInfo):
97
- """
98
- A special field serializer to dump the actual secret value when writing to JSON.
99
-
100
- Only writes secret in plaintext when `info.context == "plaintext".
101
-
102
- See [Pydantic docs](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context)
103
- """
104
- if secret is None:
105
- return None
106
-
107
- if info.context == "plaintext":
108
- return secret.get_secret_value()
109
-
110
- return str(secret)
111
-
112
96
  @model_validator(mode="after")
113
97
  def ensure_one_of(self):
114
98
  # allow all fields to be optional in subclasses
@@ -121,6 +105,76 @@ class Tokens(BaseModel):
121
105
  raise ValueError("At least one of access token and refresh token is required.")
122
106
 
123
107
 
108
+ class RetrySettings(BaseModel):
109
+ """
110
+ Retry configuration for the [Stamina library](https://stamina.hynek.me/en/stable/index.html)
111
+ """
112
+
113
+ # same defaults as AWS SDK "standard" mode:
114
+ # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#standard-retry-mode
115
+
116
+ attempts: Annotated[int, Ge(0)] = 3
117
+ timeout: Timedelta = dt.timedelta(seconds=20)
118
+
119
+ wait_initial: Timedelta = dt.timedelta(milliseconds=100)
120
+ wait_max: Timedelta = dt.timedelta(seconds=5)
121
+ wait_jitter: Timedelta = dt.timedelta(seconds=1)
122
+ wait_exp_base: Annotated[float, Gt(0)] = 2
123
+
124
+ async def retry_context(self, *retry_exc: Type[Exception]):
125
+ """
126
+ Obtain a [Stamina](https://stamina.hynek.me/en/stable/index.html) retry iterator.
127
+ """
128
+ from stamina import retry_context
129
+
130
+ retry_on = functools.partial(self.is_retriable, retry_exc=retry_exc)
131
+
132
+ ctx = retry_context(
133
+ on=retry_on,
134
+ attempts=self.attempts,
135
+ timeout=self.timeout,
136
+ wait_initial=self.wait_initial,
137
+ wait_jitter=self.wait_jitter,
138
+ wait_max=self.wait_max,
139
+ wait_exp_base=self.wait_exp_base,
140
+ )
141
+ async for attempt in ctx:
142
+ yield attempt
143
+
144
+ def is_retriable(
145
+ self,
146
+ exc: Exception,
147
+ *args,
148
+ retry_exc: tuple[Type[Exception]] = (),
149
+ **kwargs,
150
+ ) -> bool:
151
+ """
152
+ Check if the given exception can be retried
153
+ """
154
+ if retry_exc and isinstance(exc, retry_exc):
155
+ return True
156
+
157
+ return False
158
+
159
+
160
+ class HttpRetrySettings(RetrySettings):
161
+ status_codes: set[int] = {429, 500, 502, 503, 504}
162
+
163
+ def is_retriable(
164
+ self,
165
+ exc: Exception,
166
+ *args,
167
+ **kwargs,
168
+ ) -> bool:
169
+ from httpx import HTTPStatusError
170
+
171
+ if isinstance(exc, HTTPStatusError):
172
+ if exc.response.status_code in self.status_codes:
173
+ return True
174
+
175
+ return super().is_retriable(exc, *args, **kwargs)
176
+
177
+
124
178
  class AuthFlowSettings(Tokens):
125
179
  """
126
180
  Auth flow configuration
@@ -135,6 +189,20 @@ class AuthFlowSettings(Tokens):
135
189
  scope: str = "offline_access"
136
190
  client_secret: Optional[SecretStr] = None
137
191
 
192
+ # Only inject bearer token for requests to these hosts
193
+ allowed_hosts: set[str] = {
194
+ "earthscope.org",
195
+ "*.earthscope.org",
196
+ }
197
+
198
+ # Auth exchange retries
199
+ retry: HttpRetrySettings = HttpRetrySettings(
200
+ attempts=5,
201
+ timeout=dt.timedelta(seconds=30),
202
+ wait_initial=dt.timedelta(seconds=1),
203
+ wait_jitter=dt.timedelta(seconds=3),
204
+ )
205
+
138
206
  @cached_property
139
207
  def auth_flow_type(self) -> AuthFlowType:
140
208
  if self.client_secret is not None:
@@ -142,6 +210,37 @@ class AuthFlowSettings(Tokens):
142
210
 
143
211
  return AuthFlowType.DeviceCode
144
212
 
213
+ @cached_property
214
+ def allowed_host_patterns(self) -> set[str]:
215
+ """
216
+ The subset of allowed hosts that are glob patterns.
217
+
218
+ Use `is_host_allowed` to check if a host is allowed by any of these patterns.
219
+ """
220
+ return {h for h in self.allowed_hosts if "*" in h or "?" in h}
221
+
222
+ def is_host_allowed(self, host: str) -> bool:
223
+ """
224
+ Check if a host matches any pattern in the allowed hosts set.
225
+
226
+ Supports glob patterns with '?' and '*' characters (e.g., *.earthscope.org).
227
+
228
+ Args:
229
+ host: The hostname to check
230
+
231
+ Returns:
232
+ True if the host matches any allowed pattern, False otherwise
233
+ """
234
+ if host in self.allowed_hosts:
235
+ return True
236
+
237
+ for allowed_pattern in self.allowed_host_patterns:
238
+ if fnmatch.fnmatch(host, allowed_pattern):
239
+ self.allowed_hosts.add(host)
240
+ return True
241
+
242
+ return False
243
+
145
244
 
146
245
  class HttpSettings(BaseModel):
147
246
  """
@@ -157,8 +256,12 @@ class HttpSettings(BaseModel):
157
256
  timeout_connect: Timedelta = dt.timedelta(seconds=5)
158
257
  timeout_read: Timedelta = dt.timedelta(seconds=5)
159
258
 
259
+ # automatically retry requests
260
+ retry: HttpRetrySettings = HttpRetrySettings()
261
+
160
262
  # Other
161
263
  user_agent: str = f"earthscope-sdk py/{__version__}"
264
+ extra_headers: dict[str, str] = {}
162
265
 
163
266
  @cached_property
164
267
  def limits(self):
@@ -11,11 +11,15 @@ from pydantic_settings import (
11
11
  TomlConfigSettingsSource,
12
12
  )
13
13
 
14
+ from earthscope_sdk.config._bootstrap import BootstrapEnvironmentSettingsSource
14
15
  from earthscope_sdk.config._compat import LegacyEarthScopeCLISettingsSource
15
16
  from earthscope_sdk.config._util import deep_merge, get_config_dir, slugify
16
17
  from earthscope_sdk.config.error import ProfileDoesNotExistError
17
18
  from earthscope_sdk.config.models import SdkBaseSettings, Tokens
18
19
 
20
+ _BOOTSTRAP_ENV_VAR = "ES_BOOTSTRAP_SETTINGS"
21
+ """Environment variable for bootstrapping the SDK"""
22
+
19
23
  _DEFAULT_PROFILE = "default"
20
24
  """Default profile name"""
21
25
 
@@ -269,6 +273,12 @@ class SdkSettings(SdkBaseSettings, BaseSettings):
269
273
  alias = SdkSettings.model_fields["profile_name"].validation_alias
270
274
  global_settings = _GlobalSettingsSource(settings_cls, "profile_name", alias)
271
275
 
276
+ # Check for bootstrapping configuration
277
+ bootstrap_settings = BootstrapEnvironmentSettingsSource(
278
+ settings_cls,
279
+ _BOOTSTRAP_ENV_VAR,
280
+ )
281
+
272
282
  # Compatibility with earthscope-cli v0.x.x state:
273
283
  # If we find this file, we only care about the access and refresh tokens
274
284
  keep_keys = {"access_token", "refresh_token"}
@@ -281,4 +291,5 @@ class SdkSettings(SdkBaseSettings, BaseSettings):
281
291
  dotenv_settings,
282
292
  global_settings,
283
293
  legacy_settings,
294
+ bootstrap_settings,
284
295
  )
@@ -0,0 +1,29 @@
1
+ from typing import Annotated
2
+
3
+ from pydantic import PlainSerializer, SerializationInfo
4
+ from pydantic import SecretStr as _SecretStr
5
+
6
+
7
+ def _dump_secret_plaintext(secret: _SecretStr, info: SerializationInfo):
8
+ """
9
+ A special field serializer to dump the actual secret value.
10
+
11
+ Only writes secret in plaintext when `info.context == "plaintext".
12
+
13
+ See [Pydantic docs](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context)
14
+ """
15
+
16
+ if info.context == "plaintext":
17
+ return secret.get_secret_value()
18
+
19
+ return str(secret)
20
+
21
+
22
+ SecretStr = Annotated[
23
+ _SecretStr,
24
+ PlainSerializer(
25
+ _dump_secret_plaintext,
26
+ return_type=str,
27
+ when_used="json-unless-none",
28
+ ),
29
+ ]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: earthscope-sdk
3
- Version: 1.0.0b0
3
+ Version: 1.1.0
4
4
  Summary: An SDK for EarthScope API
5
5
  Author-email: EarthScope <data-help@earthscope.org>
6
6
  License: Apache License
@@ -214,16 +214,19 @@ Requires-Python: >=3.9
214
214
  Description-Content-Type: text/markdown
215
215
  License-File: LICENSE
216
216
  Requires-Dist: httpx>=0.27.0
217
- Requires-Dist: pydantic-settings[toml]>=2.7.0
217
+ Requires-Dist: pydantic-settings[toml]>=2.8.0
218
+ Requires-Dist: stamina>=24.3.0
218
219
  Provides-Extra: dev
219
220
  Requires-Dist: bumpver; extra == "dev"
220
221
  Requires-Dist: build; extra == "dev"
221
222
  Requires-Dist: pytest; extra == "dev"
222
223
  Requires-Dist: twine; extra == "dev"
223
224
  Requires-Dist: pip-tools; extra == "dev"
225
+ Requires-Dist: pre-commit; extra == "dev"
224
226
  Requires-Dist: pytest-httpx; extra == "dev"
225
227
  Requires-Dist: pytest-asyncio; extra == "dev"
226
228
  Requires-Dist: ruff; extra == "dev"
229
+ Dynamic: license-file
227
230
 
228
231
  # EarthScope SDK
229
232
 
@@ -241,7 +244,7 @@ pip install earthscope-sdk
241
244
 
242
245
  ### Usage
243
246
 
244
- For detailed usage options and examples, visit [our usage docs](docs/usage.md).
247
+ For detailed usage info and examples, visit [our SDK docs](https://docs.earthscope.org/projects/SDK).
245
248
 
246
249
  ```py
247
250
  # Import and create a client
@@ -298,7 +301,7 @@ Once refreshable credentials are available to the SDK, it will transparently han
298
301
 
299
302
  ### Same host
300
303
 
301
- If you have the [EarthScope CLI](TODO) 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
+ 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).
302
305
 
303
306
  Running `es login` will open your browser and prompt you to log in to your EarthScope account.
304
307
 
@@ -318,7 +321,7 @@ Now when you run your application, `earthscope-sdk` will find your credentials.
318
321
 
319
322
  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.
320
323
 
321
- You can still use the [EarthScope CLI](TODO) to facilitate auth for applications on other machines.
324
+ You can still use the [EarthScope CLI](https://docs.earthscope.org/projects/CLI) to facilitate auth for applications on other machines.
322
325
 
323
326
  1. Use the CLI on your primary workstation [as described above](#same-host) to log in.
324
327
 
@@ -0,0 +1,30 @@
1
+ earthscope_sdk/__init__.py,sha256=bkZcCw9euoFA9FOzEZW4H6cpa-EPH1GUsAsUEwLclIY,154
2
+ earthscope_sdk/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ earthscope_sdk/auth/auth_flow.py,sha256=gxJmCr5TAhMEss7RskYFCv6-D3A5h0Zw2ejeLJJ_9aI,10352
4
+ earthscope_sdk/auth/client_credentials_flow.py,sha256=GAvskuoEd6qHe-BtfGwYQisMedmSkDhagy4aRPVPri4,2494
5
+ earthscope_sdk/auth/device_code_flow.py,sha256=p53pgRQraToFwONPBj3O3DHAa4x4rE6rvygo_Bbj5_U,8394
6
+ earthscope_sdk/auth/error.py,sha256=eC33Bw1HaBEJE7-eI2krtE__5PxStc3EyiYO12v0kVw,693
7
+ earthscope_sdk/client/__init__.py,sha256=JotTr5oTiiOsUc0RTg82EVCUSg_-u80Qu_R0-crCXkY,139
8
+ earthscope_sdk/client/_client.py,sha256=ai7WdsTOYglA6bLkT-Wntvxlke6nSaGHwqrtg5PEy80,833
9
+ earthscope_sdk/client/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ earthscope_sdk/client/user/_base.py,sha256=8dn4pfQMwVDpF0E6dl6P6HJuNVvozUzfgUGefnPXMnw,1076
11
+ earthscope_sdk/client/user/_service.py,sha256=wRktOZF5GXajXXxij3Nkule6wvuWOV0vn4QsA1IXVHc,3063
12
+ earthscope_sdk/client/user/models.py,sha256=drZAMwOYC1NVCzBZQhNL-pPTB28SURKfoZF8HdjlIj8,1214
13
+ earthscope_sdk/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ earthscope_sdk/common/_sync_runner.py,sha256=h_A2pSEjZCLj7ov50M6cWHVoX6eXVmGzz5nX0MwLWDY,4131
15
+ earthscope_sdk/common/client.py,sha256=g5ZTNhFm33H68J9pWD5fDu760Yd5cBdfQmsbU3t8D_4,2156
16
+ earthscope_sdk/common/context.py,sha256=bt2UhSsIZGBaukA0QJiFsPhJaSUDdcT9kmAZrfTQsc4,5254
17
+ earthscope_sdk/common/service.py,sha256=SCUZVJA3jFaEPeFrOf0v9osf2UpqldhlFmirOYWJjxM,1506
18
+ earthscope_sdk/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ earthscope_sdk/config/_bootstrap.py,sha256=BvDSRccvFPFRnLS4MlE7OMLGYEgxY7znxFZ6AYgxvOE,1243
20
+ earthscope_sdk/config/_compat.py,sha256=P3F5y_Kf5zp9m9uOhl1Bp3ke6expxq4Sm9AeVaBbAHk,4610
21
+ earthscope_sdk/config/_util.py,sha256=RZ6zvKrvjUkO7i69s7AVoIDhamRg4x71CAZLnucr9QM,1249
22
+ earthscope_sdk/config/error.py,sha256=jh25q-b317lAvp32WwQw0zdYoV-MxZtg-n5FgZOMymI,95
23
+ earthscope_sdk/config/models.py,sha256=MCN9fbJKqnKHnSeg-KhUOsnXsqMxmawcOiZ62689ZS0,8723
24
+ earthscope_sdk/config/settings.py,sha256=kGsoqAgoUS2xrI0rvdqbLEsI2M0Mpbm73oEDLpJG4_Q,9205
25
+ earthscope_sdk/model/secret.py,sha256=QTyWCqXvf9ZYWaVVQcGzdt3rGtyU3sx13AlzkNE3gaU,731
26
+ earthscope_sdk-1.1.0.dist-info/licenses/LICENSE,sha256=E_MrVXxRaMQNpvZhsDuz_J9N_ux7dlL_WpYSsE391HU,11349
27
+ earthscope_sdk-1.1.0.dist-info/METADATA,sha256=t9dLQDJbJeZBd-CiUncz2Hh4JnGazSsZg19MnIP6KQ8,18122
28
+ earthscope_sdk-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ earthscope_sdk-1.1.0.dist-info/top_level.txt,sha256=zTtIT9yN3JPJF7TqmTzqQcAvZZe4pAm907DLoGa5T_E,15
30
+ earthscope_sdk-1.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,28 +0,0 @@
1
- earthscope_sdk/__init__.py,sha256=6InyrqE0KEsb_XBBKCbUIb8s0LTJ6N20HFsrO-rHVtI,156
2
- earthscope_sdk/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- earthscope_sdk/auth/auth_flow.py,sha256=HZyLrt8o3I-0KC7XRg9W0n2NAVXX7EOl9pG-5blv7sA,9613
4
- earthscope_sdk/auth/client_credentials_flow.py,sha256=1GyDSIR1OgYP4u0xZoTov1u_YhY1AzHFpOcBCzY1h6E,2769
5
- earthscope_sdk/auth/device_code_flow.py,sha256=dC5Ffj3HzBguRxSHCZYvTe1MD3C-iKf2AlanGuRKNvI,7922
6
- earthscope_sdk/auth/error.py,sha256=eC33Bw1HaBEJE7-eI2krtE__5PxStc3EyiYO12v0kVw,693
7
- earthscope_sdk/client/__init__.py,sha256=JotTr5oTiiOsUc0RTg82EVCUSg_-u80Qu_R0-crCXkY,139
8
- earthscope_sdk/client/_client.py,sha256=ai7WdsTOYglA6bLkT-Wntvxlke6nSaGHwqrtg5PEy80,833
9
- earthscope_sdk/client/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- earthscope_sdk/client/user/_base.py,sha256=nut1Ojsksohqy3X3L5FPDQ-rh-BmHLJ6sId5xVqLal0,1050
11
- earthscope_sdk/client/user/_service.py,sha256=wRktOZF5GXajXXxij3Nkule6wvuWOV0vn4QsA1IXVHc,3063
12
- earthscope_sdk/client/user/models.py,sha256=drZAMwOYC1NVCzBZQhNL-pPTB28SURKfoZF8HdjlIj8,1214
13
- earthscope_sdk/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- earthscope_sdk/common/_sync_runner.py,sha256=h_A2pSEjZCLj7ov50M6cWHVoX6eXVmGzz5nX0MwLWDY,4131
15
- earthscope_sdk/common/client.py,sha256=g5ZTNhFm33H68J9pWD5fDu760Yd5cBdfQmsbU3t8D_4,2156
16
- earthscope_sdk/common/context.py,sha256=vrCB_Ez-98Ir7c0GrCe-g7DuRCgc9vPaoRWFYf5q8Ko,5138
17
- earthscope_sdk/common/service.py,sha256=qBz6OV8rQf3WQojubEVfQ4HYeeKNN3_uIcXuOdvfH8w,1287
18
- earthscope_sdk/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- earthscope_sdk/config/_compat.py,sha256=P3F5y_Kf5zp9m9uOhl1Bp3ke6expxq4Sm9AeVaBbAHk,4610
20
- earthscope_sdk/config/_util.py,sha256=RZ6zvKrvjUkO7i69s7AVoIDhamRg4x71CAZLnucr9QM,1249
21
- earthscope_sdk/config/error.py,sha256=jh25q-b317lAvp32WwQw0zdYoV-MxZtg-n5FgZOMymI,95
22
- earthscope_sdk/config/models.py,sha256=CarL0O6RjFtufsc-q7g61uBEvETLjQr6HSmjCc0EVig,5775
23
- earthscope_sdk/config/settings.py,sha256=I2DwEvfmETcaYbSvUybs0EIih0yiJO9D46WnWzKPqbo,8812
24
- earthscope_sdk-1.0.0b0.dist-info/LICENSE,sha256=E_MrVXxRaMQNpvZhsDuz_J9N_ux7dlL_WpYSsE391HU,11349
25
- earthscope_sdk-1.0.0b0.dist-info/METADATA,sha256=jbeHzNrmHRZUGOFai1WmCDK1CQ-kWSbIsaNRjHq_WhA,17935
26
- earthscope_sdk-1.0.0b0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
27
- earthscope_sdk-1.0.0b0.dist-info/top_level.txt,sha256=zTtIT9yN3JPJF7TqmTzqQcAvZZe4pAm907DLoGa5T_E,15
28
- earthscope_sdk-1.0.0b0.dist-info/RECORD,,