Skip to content

Commit e5123c9

Browse files
authored
Merge pull request #34 from adobe-apiplatform/v2
Fix #32 and other small enhancements
2 parents cc3c304 + c8fd0c2 commit e5123c9

File tree

8 files changed

+76
-48
lines changed

8 files changed

+76
-48
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# platform artifacts
22
.*
33
!.travis.yml
4+
!.gitignore
45

56
# build and scratch areas
67
local/

HISTORY.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Fast-follow bug fix release:
1010
* Update parameter names were incorrect.
1111
* Test mode wasn't working.
1212
* [Issue 28](https://github.com/adobe-apiplatform/umapi-client.py/issues/28)
13-
* Reuse exisiting open connections across calls.
13+
* Reuse existing open connections across calls.
1414

1515
### Version 2.0.2
1616

@@ -21,3 +21,15 @@ Enhancement release:
2121
* (No Issue)
2222
* Add this HISTORY.md file to summarize releases
2323
* Add version.py file to synchronize version between setup and module.
24+
25+
### Version 2.0.3
26+
27+
Enhancement release:
28+
29+
* [Issue 32](https://github.com/adobe-apiplatform/umapi-client.py/issues/32)
30+
* change timeout default to 2 minutes
31+
* add retry after timeout.
32+
* change default create behavior to "ignoreIfAlreadyExists"
33+
* (No issue)
34+
* fix misspellings
35+
* change .gitignore so that .gitignore is not ignored

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extensions.
4040
# Getting Started
4141

4242
Before making calls to the User Management API, you must complete
43-
the following prepartory steps:
43+
the following preparatory steps:
4444

4545
1. Obtain admin access to an Adobe Enterprise Dashboard.
4646
2. Set up a private/public key pair

tests/test_connections.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,17 @@ def test_post_success_test_mode():
104104
def test_get_timeout():
105105
with mock.patch("umapi_client.connection.requests.Session.get") as mock_get:
106106
mock_get.side_effect = requests.Timeout
107-
conn = Connection(**mock_connection_params)
107+
conn = Connection(**dict(mock_connection_params, retry_max_attempts=7))
108108
pytest.raises(UnavailableError, conn.make_call, "")
109+
assert mock_get.call_count == 7
109110

110111

111112
def test_post_timeout():
112113
with mock.patch("umapi_client.connection.requests.Session.post") as mock_post:
113114
mock_post.side_effect = requests.Timeout
114-
conn = Connection(**mock_connection_params)
115+
conn = Connection(**dict(mock_connection_params, retry_max_attempts=2))
115116
pytest.raises(UnavailableError, conn.make_call, "", [3, 5])
117+
assert mock_post.call_count == 2
116118

117119

118120
def test_get_retry_header_1():
@@ -187,8 +189,7 @@ def test_get_retry_logging(log_stream):
187189
UMAPI timeout...service unavailable (code 429 on try 2)
188190
Next retry in 3 seconds...
189191
UMAPI timeout...service unavailable (code 429 on try 3)
190-
Next retry in 3 seconds...
191-
UMAPI timeout...giving up after 3 attempts.
192+
UMAPI timeout...giving up after 3 attempts (6 seconds).
192193
"""
193194

194195

@@ -208,8 +209,7 @@ def test_post_retry_logging(log_stream):
208209
UMAPI timeout...service unavailable (code 429 on try 2)
209210
Next retry in 3 seconds...
210211
UMAPI timeout...service unavailable (code 429 on try 3)
211-
Next retry in 3 seconds...
212-
UMAPI timeout...giving up after 3 attempts.
212+
UMAPI timeout...giving up after 3 attempts (6 seconds).
213213
"""
214214

215215

tests/test_functional.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def test_user_federatedid_username():
5959
def test_create_user_adobeid():
6060
user = UserAction(email="[email protected]")
6161
user.create()
62-
assert user.wire_dict() == {"do": [{"addAdobeID": {"email": "[email protected]"}}],
62+
assert user.wire_dict() == {"do": [{"addAdobeID": {"email": "[email protected]",
63+
"option": "ignoreIfAlreadyExists"}}],
6364
"user": "[email protected]",
6465
"useAdobeID": True}
6566

@@ -75,7 +76,8 @@ def test_create_user_enterpriseid():
7576
user.create(first_name="Daniel", last_name="Brotsky")
7677
assert user.wire_dict() == {"do": [{"createEnterpriseID": {"email": "[email protected]",
7778
"firstname": "Daniel", "lastname": "Brotsky",
78-
"country": "UD"}}],
79+
"country": "UD",
80+
"option": "ignoreIfAlreadyExists"}}],
7981
"user": "[email protected]"}
8082

8183

@@ -84,7 +86,8 @@ def test_create_user_federatedid():
8486
user.create(first_name="Daniel", last_name="Brotsky", country="US")
8587
assert user.wire_dict() == {"do": [{"createFederatedID": {"email": "[email protected]",
8688
"firstname": "Daniel", "lastname": "Brotsky",
87-
"country": "US"}}],
89+
"country": "US",
90+
"option": "ignoreIfAlreadyExists"}}],
8891
"user": "[email protected]"}
8992

9093

@@ -93,7 +96,8 @@ def test_create_user_federatedid_username():
9396
user.create(first_name="Daniel", last_name="Brotsky", country="US", email="[email protected]")
9497
assert user.wire_dict() == {"do": [{"createFederatedID": {"email": "[email protected]",
9598
"firstname": "Daniel", "lastname": "Brotsky",
96-
"country": "US"}}],
99+
"country": "US",
100+
"option": "ignoreIfAlreadyExists"}}],
97101
"user": "dbrotsky", "domain": "k.on-the-side.net"}
98102

99103

@@ -103,7 +107,8 @@ def test_create_user_federatedid_username_email():
103107
user.create(first_name="Daniel", last_name="Brotsky", country="US")
104108
assert user.wire_dict() == {"do": [{"createFederatedID": {"email": "[email protected]",
105109
"firstname": "Daniel", "lastname": "Brotsky",
106-
"country": "US"}}],
110+
"country": "US",
111+
"option": "ignoreIfAlreadyExists"}}],
107112
"user": "dbrotsky", "domain": "k.on-the-side.net"}
108113

109114

umapi_client/connection.py

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(self,
5151
retry_max_attempts=4,
5252
retry_first_delay=15,
5353
retry_random_delay=5,
54-
timeout_seconds=60.0,
54+
timeout_seconds=120.0,
5555
throttle_actions=10,
5656
throttle_commands=10,
5757
user_agent=None,
@@ -369,42 +369,49 @@ def call():
369369
def call():
370370
return self.session.get(self.endpoint + path, auth=self.auth, timeout=self.timeout)
371371

372-
total_time = wait_time = 0
372+
total_time = 0
373373
result = None
374374
for num_attempts in range(1, self.retry_max_attempts + 1):
375-
if wait_time > 0:
376-
sleep(wait_time)
377-
total_time += wait_time
378-
wait_time = 0
379375
try:
380376
result = call()
377+
if result.status_code == 200:
378+
return result
379+
elif result.status_code in [429, 502, 503, 504]:
380+
if self.logger: self.logger.warning("UMAPI timeout...service unavailable (code %d on try %d)",
381+
result.status_code, num_attempts)
382+
retry_wait = 0
383+
if "Retry-After" in result.headers:
384+
advice = result.headers["Retry-After"]
385+
advised_time = parsedate_tz(advice)
386+
if advised_time is not None:
387+
# header contains date
388+
retry_wait = int(mktime_tz(advised_time) - time())
389+
else:
390+
# header contains delta seconds
391+
retry_wait = int(advice)
392+
if retry_wait <= 0:
393+
# use exponential back-off with random delay
394+
delay = randint(0, self.retry_random_delay)
395+
retry_wait = (int(pow(2, num_attempts - 1)) * self.retry_first_delay) + delay
396+
elif 201 <= result.status_code < 400:
397+
raise ClientError("Unexpected HTTP Status {:d}: {}".format(result.status_code, result.text), result)
398+
elif 400 <= result.status_code < 500:
399+
raise RequestError(result)
400+
else:
401+
raise ServerError(result)
381402
except requests.Timeout:
382403
total_time += int(self.timeout)
383-
raise UnavailableError(num_attempts, total_time, result)
384-
if result.status_code == 200:
385-
return result
386-
elif result.status_code in [429, 502, 503, 504]:
387-
if self.logger: self.logger.warning("UMAPI timeout...service unavailable (code %d on try %d)",
388-
result.status_code, num_attempts)
389-
if "Retry-After" in result.headers:
390-
advice = result.headers["Retry-After"]
391-
advised_time = parsedate_tz(advice)
392-
if advised_time is not None:
393-
# header contains date
394-
wait_time = int(mktime_tz(advised_time) - time())
395-
else:
396-
# header contains delta seconds
397-
wait_time = int(advice)
398-
if wait_time <= 0:
399-
# use exponential back-off with random delay
400-
delay = randint(0, self.retry_random_delay)
401-
wait_time = (int(pow(2, num_attempts)) * self.retry_first_delay) + delay
402-
if self.logger: self.logger.warning("Next retry in %d seconds...", wait_time)
403-
elif 201 <= result.status_code < 400:
404-
raise ClientError("Unexpected HTTP Status {:d}: {}".format(result.status_code, result.text), result)
405-
elif 400 <= result.status_code < 500:
406-
raise RequestError(result)
407-
else:
408-
raise ServerError(result)
409-
if self.logger: self.logger.error("UMAPI timeout...giving up after %d attempts.", self.retry_max_attempts)
404+
if self.logger: self.logger.warning("UMAPI connection timeout...(%d seconds on try %d)",
405+
self.timeout, num_attempts)
406+
retry_wait = 0
407+
result = None
408+
if num_attempts < self.retry_max_attempts:
409+
if retry_wait > 0:
410+
if self.logger: self.logger.warning("Next retry in %d seconds...", retry_wait)
411+
sleep(retry_wait)
412+
total_time += retry_wait
413+
else:
414+
if self.logger: self.logger.warning("Immediate retry...", retry_wait)
415+
if self.logger: self.logger.error("UMAPI timeout...giving up after %d attempts (%d seconds).",
416+
self.retry_max_attempts, total_time)
410417
raise UnavailableError(self.retry_max_attempts, total_time, result)

umapi_client/functional.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ def __init__(self, id_type=IdentityTypes.adobeID, email=None, username=None, dom
127127
Action.__init__(self, user=email, **kwargs)
128128

129129
def create(self, first_name=None, last_name=None, country=None, email=None,
130-
on_conflict=IfAlreadyExistsOptions.errorIfAlreadyExists):
130+
on_conflict=IfAlreadyExistsOptions.ignoreIfAlreadyExists):
131131
"""
132132
Create the user on the Adobe back end.
133+
See [Issue 32](https://github.com/adobe-apiplatform/umapi-client.py/issues/32): because
134+
we retry create calls if they time out, the default conflict handling for creation is to ignore the
135+
create call if the user already exists (possibly from an earlier call that timed out).
133136
:param first_name: (optional) user first name
134137
:param last_name: (optional) user last name
135138
:param country: (optional except for Federated ID) user 2-letter ISO country code

umapi_client/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1919
# SOFTWARE.
2020

21-
__version__ = "2.0.2"
21+
__version__ = "2.0.3"

0 commit comments

Comments
 (0)