Slide 1

Slide 1 text

RETRY BEST PRACTICES Fabio Fleitas & Peter Hadlaw @ Tesorio August 11, 2017 https://www.tesorio.com/careers

Slide 2

Slide 2 text

FETCH GITHUB USERS import requests def fetch_github_users(): url = 'https://api.github.com/users' while url: response = requests.get(url) response.raise_for_status() data = response.json() for user in data: yield user url = next_page_url(response)

Slide 3

Slide 3 text

$ pip install tenacity

Slide 4

Slide 4 text

RETRY ... EVERYTHING from tenacity import retry @retry def fetch_github_users(): url = 'https://api.github.com/users' while url: response = requests.get(url) response.raise_for_status() data = response.json() for user in data: yield user url = next_page_url(response)

Slide 5

Slide 5 text

RETRY INDIVIDUAL REQUESTS @retry def get_data(url): response = requests.get(url) response.raise_for_status() return { 'results': response.json(), 'next_url': next_page_url(response), } def fetch_github_users(): url = 'https://api.github.com/users' while url: data = get_data(url) for user in data['results']: yield user url = data['next_url']

Slide 6

Slide 6 text

RATE LIMITING

Slide 7

Slide 7 text

class RateLimitExceededError(Exception): pass def validate_response(response): is_rate_limited = ( response.headers['X-RateLimit-Remaining'] == '0') if is_rate_limited: raise RateLimitExceededError() response.raise_for_status()

Slide 8

Slide 8 text

from tenacity import retry_if_exception_type, wait_fixed @retry( retry=retry_if_exception_type(RateLimitExceededError), wait=wait_fixed(10)) def get_data(url): response = requests.get(url) validate_response(response) # ...

Slide 9

Slide 9 text

BETTER RATE LIMITING import time def validate_response(response): # ... if is_rate_limited: rate_limit_reset_time = ( int(response.headers['X-RateLimit-Reset'])) time_to_sleep = rate_limit_reset_time - time.time() time.sleep(time_to_sleep) raise RateLimitExceededError() # ...

Slide 10

Slide 10 text

GitHub is down...

Slide 11

Slide 11 text

class GitHubIsDownError(Exception): pass def validate_response(response): # ... github_is_down = response.status_code == 500 if github_is_down: raise GitHubIsDownError() # ...

Slide 12

Slide 12 text

from tenacity import wait_exponential @retry(retry=retry_if_exception_type(RateLimitExceededError)) @retry( retry=retry_if_exception_type(GitHubIsDownError), # Wait 2^x * 1 second between each retry wait=wait_exponential(multiplier=1)) def get_data(url): # ...

Slide 13

Slide 13 text

RETRYING POST REQUESTS The goal is to have idempotent POST requests.

Slide 14

Slide 14 text

IDEMPOTENCY In computer science, the term idempotent is used more comprehensively to describe an operation that will produce the same results if executed once or multiple times. More details: https://stripe.com/blog/idempotency

Slide 15

Slide 15 text

import uuid import stripe stripe.api_key = "APIKEY" @retry(retry=retry_if_exception_type(RateLimitExceededError)) @retry( retry=retry_if_exception_type(StripeIsDownError), wait=wait_exponential(multiplier=1)) def send_request(idempotency_key, **kwargs): return stripe.Charge.create( idempotency_key=idempotency_key, **kwargs)

Slide 16

Slide 16 text

def charge_customer(): idempotency_key = uuid.uuid4() return send_request( idempotency_key, amount=9001, currency="usd", description="Charge for [email protected]", source="tok_mastercard", # obtained with Stripe.js )

Slide 17

Slide 17 text

WE'RE HIRING https://www.tesorio.com/careers