earthscope-sdk 1.0.0b0__tar.gz → 1.0.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/PKG-INFO +5 -3
  2. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/pyproject.toml +7 -3
  3. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/__init__.py +1 -1
  4. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/auth/auth_flow.py +19 -4
  5. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/client/user/_base.py +2 -2
  6. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/common/service.py +5 -0
  7. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/config/models.py +85 -2
  8. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk.egg-info/PKG-INFO +5 -3
  9. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk.egg-info/requires.txt +2 -1
  10. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/tests/test_auth.py +30 -0
  11. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/tests/test_client.py +32 -0
  12. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/tests/test_settings.py +40 -2
  13. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/LICENSE +0 -0
  14. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/README.md +0 -0
  15. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/setup.cfg +0 -0
  16. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/setup.py +0 -0
  17. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/auth/__init__.py +0 -0
  18. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/auth/client_credentials_flow.py +0 -0
  19. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/auth/device_code_flow.py +0 -0
  20. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/auth/error.py +0 -0
  21. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/client/__init__.py +0 -0
  22. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/client/_client.py +0 -0
  23. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/client/user/__init__.py +0 -0
  24. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/client/user/_service.py +0 -0
  25. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/client/user/models.py +0 -0
  26. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/common/__init__.py +0 -0
  27. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/common/_sync_runner.py +0 -0
  28. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/common/client.py +0 -0
  29. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/common/context.py +0 -0
  30. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/config/__init__.py +0 -0
  31. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/config/_compat.py +0 -0
  32. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/config/_util.py +0 -0
  33. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/config/error.py +0 -0
  34. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk/config/settings.py +0 -0
  35. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk.egg-info/SOURCES.txt +0 -0
  36. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk.egg-info/dependency_links.txt +0 -0
  37. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/src/earthscope_sdk.egg-info/top_level.txt +0 -0
  38. {earthscope_sdk-1.0.0b0 → earthscope_sdk-1.0.0b1}/tests/test_context.py +0 -0
@@ -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.0.0b1
4
4
  Summary: An SDK for EarthScope API
5
5
  Author-email: EarthScope <data-help@earthscope.org>
6
6
  License: Apache License
@@ -214,7 +214,8 @@ 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"
@@ -224,6 +225,7 @@ Requires-Dist: pip-tools; extra == "dev"
224
225
  Requires-Dist: pytest-httpx; extra == "dev"
225
226
  Requires-Dist: pytest-asyncio; extra == "dev"
226
227
  Requires-Dist: ruff; extra == "dev"
228
+ Dynamic: license-file
227
229
 
228
230
  # EarthScope SDK
229
231
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "earthscope-sdk"
7
- version = "1.0.0b0"
7
+ version = "1.0.0b1"
8
8
  description = "An SDK for EarthScope API"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "EarthScope", email = "data-help@earthscope.org" }]
@@ -17,7 +17,11 @@ classifiers = [
17
17
  ]
18
18
 
19
19
  #Suggestion not to pin dependencies since the package should work in many different python environments
20
- dependencies = ["httpx>=0.27.0", "pydantic-settings[toml]>=2.7.0"]
20
+ dependencies = [
21
+ "httpx>=0.27.0",
22
+ "pydantic-settings[toml]>=2.8.0",
23
+ "stamina>=24.3.0",
24
+ ]
21
25
  requires-python = ">=3.9"
22
26
 
23
27
  [project.optional-dependencies]
@@ -36,7 +40,7 @@ dev = [
36
40
  Homepage = "https://gitlab.com/earthscope/public/earthscope-sdk"
37
41
 
38
42
  [tool.bumpver]
39
- current_version = "1.0.0b0"
43
+ current_version = "1.0.0b1"
40
44
  version_pattern = "MAJOR.MINOR.PATCH[PYTAGNUM]"
41
45
  commit_message = "chore: bump version {old_version} -> {new_version}"
42
46
  commit = true
@@ -1,4 +1,4 @@
1
- __version__ = "1.0.0b0"
1
+ __version__ = "1.0.0b1"
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)
@@ -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)
@@ -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)
@@ -1,11 +1,13 @@
1
1
  import base64
2
2
  import binascii
3
3
  import datetime as dt
4
+ import functools
4
5
  from contextlib import suppress
5
6
  from enum import Enum
6
7
  from functools import cached_property
7
- from typing import Annotated, Any, Optional, Union
8
+ from typing import Annotated, Any, Optional, Type, Union
8
9
 
10
+ from annotated_types import Ge, Gt
9
11
  from pydantic import (
10
12
  AliasChoices,
11
13
  BaseModel,
@@ -86,7 +88,7 @@ class Tokens(BaseModel):
86
88
  return None
87
89
 
88
90
  with suppress(IndexError, binascii.Error, ValidationError):
89
- payload_b64 = self.access_token.get_secret_value().split(".")[1]
91
+ payload_b64 = self.access_token.get_secret_value().split(".", 2)[1]
90
92
  payload = base64.b64decode(payload_b64 + "==") # extra padding
91
93
  return AccessTokenBody.model_validate_json(payload)
92
94
 
@@ -121,6 +123,76 @@ class Tokens(BaseModel):
121
123
  raise ValueError("At least one of access token and refresh token is required.")
122
124
 
123
125
 
126
+ class RetrySettings(BaseModel):
127
+ """
128
+ Retry configuration for the [Stamina library](https://stamina.hynek.me/en/stable/index.html)
129
+ """
130
+
131
+ # same defaults as AWS SDK "standard" mode:
132
+ # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#standard-retry-mode
133
+
134
+ attempts: Annotated[int, Ge(0)] = 3
135
+ timeout: Timedelta = dt.timedelta(seconds=20)
136
+
137
+ wait_initial: Timedelta = dt.timedelta(milliseconds=100)
138
+ wait_max: Timedelta = dt.timedelta(seconds=5)
139
+ wait_jitter: Timedelta = dt.timedelta(seconds=1)
140
+ wait_exp_base: Annotated[float, Gt(0)] = 2
141
+
142
+ async def retry_context(self, *retry_exc: Type[Exception]):
143
+ """
144
+ Obtain a [Stamina](https://stamina.hynek.me/en/stable/index.html) retry iterator.
145
+ """
146
+ from stamina import retry_context
147
+
148
+ retry_on = functools.partial(self.is_retriable, retry_exc=retry_exc)
149
+
150
+ ctx = retry_context(
151
+ on=retry_on,
152
+ attempts=self.attempts,
153
+ timeout=self.timeout,
154
+ wait_initial=self.wait_initial,
155
+ wait_jitter=self.wait_jitter,
156
+ wait_max=self.wait_max,
157
+ wait_exp_base=self.wait_exp_base,
158
+ )
159
+ async for attempt in ctx:
160
+ yield attempt
161
+
162
+ def is_retriable(
163
+ self,
164
+ exc: Exception,
165
+ *args,
166
+ retry_exc: tuple[Type[Exception]] = (),
167
+ **kwargs,
168
+ ) -> bool:
169
+ """
170
+ Check if the given exception can be retried
171
+ """
172
+ if retry_exc and isinstance(exc, retry_exc):
173
+ return True
174
+
175
+ return False
176
+
177
+
178
+ class HttpRetrySettings(RetrySettings):
179
+ status_codes: set[int] = {429, 500, 502, 503, 504}
180
+
181
+ def is_retriable(
182
+ self,
183
+ exc: Exception,
184
+ *args,
185
+ **kwargs,
186
+ ) -> bool:
187
+ from httpx import HTTPStatusError
188
+
189
+ if isinstance(exc, HTTPStatusError):
190
+ if exc.response.status_code in self.status_codes:
191
+ return True
192
+
193
+ return super().is_retriable(exc, *args, **kwargs)
194
+
195
+
124
196
  class AuthFlowSettings(Tokens):
125
197
  """
126
198
  Auth flow configuration
@@ -135,6 +207,14 @@ class AuthFlowSettings(Tokens):
135
207
  scope: str = "offline_access"
136
208
  client_secret: Optional[SecretStr] = None
137
209
 
210
+ # Auth exchange retries
211
+ retry: HttpRetrySettings = HttpRetrySettings(
212
+ attempts=5,
213
+ timeout=dt.timedelta(seconds=30),
214
+ wait_initial=dt.timedelta(seconds=1),
215
+ wait_jitter=dt.timedelta(seconds=3),
216
+ )
217
+
138
218
  @cached_property
139
219
  def auth_flow_type(self) -> AuthFlowType:
140
220
  if self.client_secret is not None:
@@ -157,6 +237,9 @@ class HttpSettings(BaseModel):
157
237
  timeout_connect: Timedelta = dt.timedelta(seconds=5)
158
238
  timeout_read: Timedelta = dt.timedelta(seconds=5)
159
239
 
240
+ # automatically retry requests
241
+ retry: HttpRetrySettings = HttpRetrySettings()
242
+
160
243
  # Other
161
244
  user_agent: str = f"earthscope-sdk py/{__version__}"
162
245
 
@@ -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.0.0b1
4
4
  Summary: An SDK for EarthScope API
5
5
  Author-email: EarthScope <data-help@earthscope.org>
6
6
  License: Apache License
@@ -214,7 +214,8 @@ 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"
@@ -224,6 +225,7 @@ Requires-Dist: pip-tools; extra == "dev"
224
225
  Requires-Dist: pytest-httpx; extra == "dev"
225
226
  Requires-Dist: pytest-asyncio; extra == "dev"
226
227
  Requires-Dist: ruff; extra == "dev"
228
+ Dynamic: license-file
227
229
 
228
230
  # EarthScope SDK
229
231
 
@@ -1,5 +1,6 @@
1
1
  httpx>=0.27.0
2
- pydantic-settings[toml]>=2.7.0
2
+ pydantic-settings[toml]>=2.8.0
3
+ stamina>=24.3.0
3
4
 
4
5
  [dev]
5
6
  bumpver
@@ -2,6 +2,7 @@ import time
2
2
  import webbrowser
3
3
 
4
4
  import pytest
5
+ from pytest_httpx import HTTPXMock
5
6
 
6
7
  from earthscope_sdk.auth.client_credentials_flow import ClientCredentialsFlow
7
8
  from earthscope_sdk.auth.device_code_flow import DeviceCodeFlow
@@ -17,6 +18,8 @@ from .util import (
17
18
  is_pipeline,
18
19
  missing_m2m_creds,
19
20
  missing_refresh_token,
21
+ register_mock_token_response,
22
+ retries_enabled,
20
23
  )
21
24
 
22
25
 
@@ -135,6 +138,33 @@ class TestAuthDeviceCodeFlow:
135
138
  flow.refresh_if_necessary()
136
139
  assert flow.access_token == at
137
140
 
141
+ @pytest.mark.asyncio
142
+ async def test_refresh_retry(
143
+ self,
144
+ httpx_mock: HTTPXMock,
145
+ ):
146
+ httpx_mock.add_response(429)
147
+ httpx_mock.add_response(500)
148
+ httpx_mock.add_response(502)
149
+ httpx_mock.add_response(504)
150
+ mock_token_body = register_mock_token_response(httpx_mock)
151
+
152
+ settings = SdkSettings(
153
+ oauth2=AuthFlowSettings(
154
+ audience=AUDIENCE,
155
+ domain=DOMAIN,
156
+ refresh_token="mock-refresh-token",
157
+ )
158
+ )
159
+ flow = DeviceCodeFlow(SdkContext(settings))
160
+
161
+ with retries_enabled(5):
162
+ await flow.async_refresh()
163
+
164
+ assert len(httpx_mock.get_requests()) == 5
165
+ assert flow.refresh_token == "mock-refresh-token"
166
+ assert flow.access_token_body == mock_token_body
167
+
138
168
 
139
169
  class TestAuthClientCredentialsFlow:
140
170
  def test_no_device_code_with_m2m_creds(self):
@@ -8,6 +8,7 @@ from earthscope_sdk.client import AsyncEarthScopeClient, EarthScopeClient
8
8
  from earthscope_sdk.client.user.models import AwsTemporaryCredentials, UserProfile
9
9
  from earthscope_sdk.common.context import SdkContext
10
10
  from earthscope_sdk.config.settings import SdkSettings
11
+ from tests.util import retries_enabled
11
12
 
12
13
 
13
14
  class TestSyncClient:
@@ -128,3 +129,34 @@ class TestAsyncClient:
128
129
 
129
130
  c_resp = await client.user.get_aws_credentials(force=True)
130
131
  assert c_resp == c2, "got new credentials when forced"
132
+
133
+ @pytest.mark.asyncio
134
+ async def test_retries(
135
+ self,
136
+ mock_settings: SdkSettings,
137
+ httpx_mock: HTTPXMock,
138
+ ):
139
+ # reuse context
140
+ ctx = SdkContext(settings=mock_settings)
141
+
142
+ httpx_mock.add_response(429)
143
+ httpx_mock.add_response(429)
144
+
145
+ u = UserProfile(
146
+ first_name="Jane",
147
+ last_name="Doe",
148
+ country_code="US",
149
+ region_code="CO",
150
+ institution="EarthScope Consortium",
151
+ work_sector="non-profit",
152
+ user_id="user-id-123",
153
+ primary_email="jane.doe@earthscope.org",
154
+ created_at="2024-01-01T00:00:00Z",
155
+ updated_at="2024-03-01T00:00:00Z",
156
+ )
157
+ httpx_mock.add_response(json=u.model_dump(mode="json"))
158
+
159
+ with retries_enabled(3):
160
+ async with AsyncEarthScopeClient(ctx=ctx) as client:
161
+ u_resp = await client.user.get_profile()
162
+ assert u == u_resp
@@ -8,7 +8,12 @@ from pytest import MonkeyPatch
8
8
  from earthscope_sdk import __version__
9
9
  from earthscope_sdk.config._compat import _get_legacy_auth_state_path
10
10
  from earthscope_sdk.config.error import ProfileDoesNotExistError
11
- from earthscope_sdk.config.models import AuthFlowSettings, HttpSettings, Tokens
11
+ from earthscope_sdk.config.models import (
12
+ AuthFlowSettings,
13
+ HttpRetrySettings,
14
+ HttpSettings,
15
+ Tokens,
16
+ )
12
17
  from earthscope_sdk.config.settings import SdkSettings, _get_config_toml_path
13
18
 
14
19
 
@@ -41,9 +46,21 @@ class TestSdkSettings:
41
46
  assert s.http.max_keepalive_connections is None
42
47
  assert s.http.keepalive_expiry.total_seconds() == 5.0
43
48
  assert s.http.timeout_read.total_seconds() == 5.0
49
+ assert s.http.retry.attempts == 3
50
+ assert s.http.retry.timeout.total_seconds() == 20.0
51
+ assert s.http.retry.wait_initial.total_seconds() == 0.1
52
+ assert s.http.retry.wait_max.total_seconds() == 5.0
53
+ assert s.http.retry.wait_jitter.total_seconds() == 1.0
54
+ assert s.http.retry.wait_exp_base == 2
44
55
  assert s.oauth2.client_secret is None
45
56
  assert s.oauth2.access_token is None
46
57
  assert s.oauth2.refresh_token is None
58
+ assert s.oauth2.retry.attempts == 5
59
+ assert s.oauth2.retry.timeout.total_seconds() == 30.0
60
+ assert s.oauth2.retry.wait_initial.total_seconds() == 1.0
61
+ assert s.oauth2.retry.wait_max.total_seconds() == 5.0
62
+ assert s.oauth2.retry.wait_jitter.total_seconds() == 3.0
63
+ assert s.oauth2.retry.wait_exp_base == 2
47
64
 
48
65
  def test_profile_does_not_exist_init(self):
49
66
  with pytest.raises(ProfileDoesNotExistError):
@@ -228,24 +245,45 @@ class TestSdkSettingsProfiles:
228
245
  http.max_keepalive_connections = 11
229
246
  http.timeout_read = 11.1
230
247
  http.keepalive_expiry = 11.1
248
+
249
+ http.retry.attempts = 11
250
+ http.retry.timeout = 11.1
251
+ http.retry.wait_jitter = 11.1
252
+ http.retry.wait_initial = 11.1
231
253
 
232
254
  [profile.pytest]
233
255
  http.max_connections = 22
234
256
  http.max_keepalive_connections = 22
235
257
  http.keepalive_expiry = 22.2
258
+ http.retry.timeout = 22.2
259
+ http.retry.wait_jitter = 22.2
260
+ http.retry.wait_initial = 22.2
236
261
  """)
237
262
  )
238
263
 
239
264
  monkeypatch.setenv("ES_HTTP__MAX_CONNECTIONS", "33")
240
265
  monkeypatch.setenv("ES_HTTP__KEEPALIVE_EXPIRY", "33.3")
266
+ monkeypatch.setenv("ES_HTTP__RETRY__WAIT_JITTER", "33.3")
267
+ monkeypatch.setenv("ES_HTTP__RETRY__WAIT_INITIAL", "33.3")
241
268
 
242
- s = SdkSettings(profile_name="pytest", http=HttpSettings(keepalive_expiry=44.4))
269
+ s = SdkSettings(
270
+ profile_name="pytest",
271
+ http=HttpSettings(
272
+ keepalive_expiry=44.4,
273
+ retry=HttpRetrySettings(wait_initial=44.4),
274
+ ),
275
+ )
243
276
 
244
277
  assert s.http.keepalive_expiry.total_seconds() == 44.4
245
278
  assert s.http.max_connections == 33
246
279
  assert s.http.max_keepalive_connections == 22
247
280
  assert s.http.timeout_read.total_seconds() == 11.1
248
281
 
282
+ assert s.http.retry.attempts == 11
283
+ assert s.http.retry.timeout.total_seconds() == 22.2
284
+ assert s.http.retry.wait_jitter.total_seconds() == 33.3
285
+ assert s.http.retry.wait_initial.total_seconds() == 44.4
286
+
249
287
 
250
288
  class TestTokens:
251
289
  def test_defaults(self):