Skip to content

Commit 351834e

Browse files
authored
FFM-10063 Various bug fixes and enhancements (#86)
1 parent ea8be63 commit 351834e

File tree

13 files changed

+670
-204
lines changed

13 files changed

+670
-204
lines changed

featureflags/api/default/authenticate.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Any, Dict, Optional, Union
22

3+
from featureflags.config import RETRYABLE_CODES
34
from featureflags.sdk_logging_codes import warn_auth_retying
45
from featureflags.util import log
56

@@ -14,7 +15,11 @@
1415

1516

1617
class UnrecoverableAuthenticationException(Exception):
17-
pass
18+
def __init__(self, message):
19+
self.message = message
20+
21+
def __str__(self):
22+
return f"UnrecoverableAuthenticationException: {self.message}"
1823

1924

2025
def _get_kwargs(
@@ -75,7 +80,7 @@ def sync_detailed(
7580

7681
def handle_http_result(response):
7782
code = response.status_code
78-
if code in {408, 425, 429, 500, 502, 503, 504}:
83+
if code in RETRYABLE_CODES:
7984
return True
8085
else:
8186
log.error(

featureflags/api/default/get_all_segments.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@
44

55
from featureflags.api.client import AuthenticatedClient
66
from featureflags.api.types import Response
7+
from featureflags.config import RETRYABLE_CODES
78
from featureflags.evaluations.segment import Segment
89

10+
from tenacity import retry_if_result, wait_exponential, \
11+
stop_after_attempt, Retrying, retry_all
12+
13+
from featureflags.sdk_logging_codes import warning_fetch_all_segments_retrying
14+
from featureflags.util import log
15+
16+
MAX_RETRY_ATTEMPTS = 10
17+
918

1019
def _get_kwargs(
1120
*,
@@ -67,13 +76,36 @@ def sync_detailed(
6776
**params
6877
)
6978

70-
response = httpx.get(
71-
**kwargs,
79+
retryer = Retrying(
80+
wait=wait_exponential(multiplier=1, min=4, max=10),
81+
retry=retry_all(
82+
retry_if_result(lambda r: r.status_code != 200),
83+
retry_if_result(handle_http_result)
84+
),
85+
before_sleep=lambda retry_state: warning_fetch_all_segments_retrying(
86+
retry_state.attempt_number,
87+
retry_state.outcome.result()),
88+
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
7289
)
7390

91+
response = retryer(httpx.get, **kwargs)
92+
7493
return _build_response(response=response)
7594

7695

96+
def handle_http_result(response):
97+
code = response.status_code
98+
if code in RETRYABLE_CODES:
99+
return True
100+
else:
101+
# TODO - revisit these error logs, we need to log the SDK code, but
102+
# do we want to do it here, or by the caller?
103+
log.error(
104+
f'Fetching all groups received code #{code} and '
105+
'will not retry')
106+
return False
107+
108+
77109
def sync(
78110
*,
79111
client: AuthenticatedClient,

featureflags/api/default/get_feature_config.py

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@
44

55
from featureflags.api.client import AuthenticatedClient
66
from featureflags.api.types import Response
7+
from featureflags.config import RETRYABLE_CODES
78
from featureflags.evaluations.feature import FeatureConfig
89

10+
from tenacity import retry_if_result, wait_exponential, \
11+
stop_after_attempt, Retrying, retry_all
12+
13+
from featureflags.sdk_logging_codes import warning_fetch_all_features_retrying
14+
from featureflags.util import log
15+
16+
MAX_RETRY_ATTEMPTS = 10
17+
918

1019
def _get_kwargs(
11-
*,
12-
client: AuthenticatedClient,
13-
environment_uuid: str,
14-
**params: Any
20+
*,
21+
client: AuthenticatedClient,
22+
environment_uuid: str,
23+
**params: Any
1524
) -> Dict[str, Any]:
1625
url = "{}/client/env/{environmentUUID}/feature-configs".format(
1726
client.base_url, environmentUUID=environment_uuid
@@ -35,8 +44,8 @@ def _get_kwargs(
3544

3645

3746
def _parse_response(
38-
*,
39-
response: httpx.Response
47+
*,
48+
response: httpx.Response
4049
) -> Optional[List[FeatureConfig]]:
4150
if response.status_code == 200:
4251
response_200 = []
@@ -51,8 +60,8 @@ def _parse_response(
5160

5261

5362
def _build_response(
54-
*,
55-
response: httpx.Response
63+
*,
64+
response: httpx.Response
5665
) -> Response[List[FeatureConfig]]:
5766
return Response(
5867
status_code=response.status_code,
@@ -63,29 +72,52 @@ def _build_response(
6372

6473

6574
def sync_detailed(
66-
*,
67-
client: AuthenticatedClient,
68-
environment_uuid: str,
69-
**params: Any
75+
*,
76+
client: AuthenticatedClient,
77+
environment_uuid: str,
78+
**params: Any
7079
) -> Response[List[FeatureConfig]]:
7180
kwargs = _get_kwargs(
7281
client=client,
7382
environment_uuid=environment_uuid,
7483
**params
7584
)
7685

77-
response = httpx.get(
78-
**kwargs,
86+
retryer = Retrying(
87+
wait=wait_exponential(multiplier=1, min=4, max=10),
88+
retry=retry_all(
89+
retry_if_result(lambda r: r.status_code != 200),
90+
retry_if_result(handle_http_result)
91+
),
92+
before_sleep=lambda retry_state: warning_fetch_all_features_retrying(
93+
retry_state.attempt_number,
94+
retry_state.outcome.result()),
95+
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
7996
)
8097

98+
response = retryer(httpx.get, **kwargs)
99+
81100
return _build_response(response=response)
82101

83102

103+
def handle_http_result(response):
104+
code = response.status_code
105+
if code in RETRYABLE_CODES:
106+
return True
107+
else:
108+
# TODO - revisit these error logs, we need to log the SDK code, but
109+
# do we want to do it here, or by the caller?
110+
log.error(
111+
f'Fetching all features received code #{code} and '
112+
'will not retry')
113+
return False
114+
115+
84116
def sync(
85-
*,
86-
client: AuthenticatedClient,
87-
environment_uuid: str,
88-
**params: Any
117+
*,
118+
client: AuthenticatedClient,
119+
environment_uuid: str,
120+
**params: Any
89121
) -> Optional[List[FeatureConfig]]:
90122
"""All feature flags with activations in project environment"""
91123

@@ -97,10 +129,10 @@ def sync(
97129

98130

99131
async def asyncio_detailed(
100-
*,
101-
client: AuthenticatedClient,
102-
environment_uuid: str,
103-
**params: Any
132+
*,
133+
client: AuthenticatedClient,
134+
environment_uuid: str,
135+
**params: Any
104136
) -> Response[List[FeatureConfig]]:
105137
kwargs = _get_kwargs(
106138
client=client,
@@ -115,10 +147,10 @@ async def asyncio_detailed(
115147

116148

117149
async def asyncio(
118-
*,
119-
client: AuthenticatedClient,
120-
environment_uuid: str,
121-
**params: Any
150+
*,
151+
client: AuthenticatedClient,
152+
environment_uuid: str,
153+
**params: Any
122154
) -> Optional[List[FeatureConfig]]:
123155
"""All feature flags with activations in project environment"""
124156

featureflags/api/default/get_feature_config_by_identifier.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@
44

55
from featureflags.api.client import AuthenticatedClient
66
from featureflags.api.types import Response
7+
from featureflags.config import RETRYABLE_CODES
78
from featureflags.evaluations.feature import FeatureConfig
89

10+
from tenacity import retry_if_result, wait_exponential, \
11+
stop_after_attempt, Retrying, retry_all
12+
13+
from featureflags.sdk_logging_codes import warning_fetch_feature_by_id_retrying
14+
from featureflags.util import log
15+
16+
MAX_RETRY_ATTEMPTS = 10
17+
918

1019
def _get_kwargs(
1120
*,
@@ -69,13 +78,36 @@ def sync_detailed(
6978
**params
7079
)
7180

72-
response = httpx.get(
73-
**kwargs,
81+
retryer = Retrying(
82+
wait=wait_exponential(multiplier=1, min=4, max=10),
83+
retry=retry_all(
84+
retry_if_result(lambda r: r.status_code != 200),
85+
retry_if_result(handle_http_result)
86+
),
87+
before_sleep=lambda retry_state: warning_fetch_feature_by_id_retrying(
88+
retry_state.attempt_number,
89+
retry_state.outcome.result()),
90+
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
7491
)
7592

93+
response = retryer(httpx.get, **kwargs)
94+
7695
return _build_response(response=response)
7796

7897

98+
def handle_http_result(response):
99+
code = response.status_code
100+
if code in RETRYABLE_CODES:
101+
return True
102+
else:
103+
# TODO - revisit these error logs, we need to log the SDK code, but
104+
# do we want to do it here, or by the caller?
105+
log.error(
106+
f'Fetching feature by identifier received code #{code} and '
107+
'will not retry')
108+
return False
109+
110+
79111
def sync(
80112
*,
81113
client: AuthenticatedClient,

0 commit comments

Comments
 (0)