11#!/usr/bin/env python3.7
2+ from datetime import datetime , timezone
23import io
3- import tarfile
4+ import json
45import platform
5- import requests
66import sys
7+ import tarfile
8+ import time
9+ from urllib .request import urlopen
10+ from urllib .error import HTTPError , URLError
711
812
13+ BACKOFF_TIME = 5 # in seconds
14+ MAX_RETRIES = 5
15+
916def download_and_extract (url ):
10- resp = requests .get (url )
11- print ('extracting...' )
12- file = tarfile .open (fileobj = io .BytesIO (resp .content ), mode = 'r:gz' )
13- file .extractall (path = 'bin/' )
17+ try :
18+ with urlopen (url ) as request :
19+ status = request .status
20+ response = request .read ()
21+ except HTTPError as err :
22+ status = err .code
23+ response = err .reason
24+ except URLError as err :
25+ print (err .reason )
26+ time .sleep (BACKOFF_TIME )
27+ return False
28+ if status >= 400 :
29+ print (f"Unexpected { status } status from download server." )
30+ time .sleep (BACKOFF_TIME )
31+ return False
32+
33+ print ("extracting..." )
34+ with tarfile .open (fileobj = io .BytesIO (response ), mode = "r:*" ) as file :
35+ file .extractall (path = "bin/" )
36+ return True
1437
1538
1639def get_os ():
@@ -23,27 +46,56 @@ def get_os():
2346
2447
2548def get_arch ():
26- return ' 64bit' if sys .maxsize > 2 ** 32 else ' 32bit'
49+ return " 64bit" if sys .maxsize > 2 ** 32 else " 32bit"
2750
2851
2952def fetch_configlet ():
30- latest = "https://api.github.com/repos/exercism/configlet/releases/latest"
31- resp = requests .get (latest )
32- data = resp .json ()
53+ try :
54+ with urlopen (
55+ "https://api.github.com/repos/exercism/configlet/releases/latest"
56+ ) as request :
57+ status = request .status
58+ headers = dict (request .getheaders ())
59+ response = request .read ()
60+ except HTTPError as err :
61+ status = err .code
62+ headers = err .headers
63+ response = err .reason
64+ except URLError as err :
65+ print (err .reason )
66+ time .sleep (BACKOFF_TIME )
67+ return 1
68+ if status >= 500 :
69+ print (f"Sleeping due to { status } response from API." )
70+ time .sleep (BACKOFF_TIME )
71+ return 1
72+ if status == 403 :
73+ wait_until = datetime .fromtimestamp (
74+ int (headers ["X-RateLimit-Reset" ]), timezone .utc
75+ )
76+ delta = wait_until - datetime .now (timezone .utc )
77+ seconds = delta .total_seconds ()
78+ wait = seconds + 5 if seconds > BACKOFF_TIME else BACKOFF_TIME
79+ print (f"Rate limited, sleeping { wait } seconds." )
80+ time .sleep (wait )
81+ return 1
82+ if status >= 400 :
83+ sys .exit (f"Received unexpected { status } response from API." )
84+ data = json .loads (response .decode ("utf-8" ))
3385 version = data ["tag_name" ]
3486 machine_info = f"{ get_os ()} -{ get_arch ()} "
3587 name = f"configlet-{ machine_info } .tgz"
3688 for asset in data ["assets" ]:
3789 if asset ["name" ] == name :
3890 print (f"Downloading configlet { version } for { machine_info } " )
39- download_and_extract (asset ["browser_download_url" ])
40- return 0
91+ for _ in range (MAX_RETRIES ):
92+ if download_and_extract (asset ["browser_download_url" ]):
93+ return 0
4194 return 1
4295
4396
4497def main ():
45- max_retries = 5
46- for i in range (max_retries ):
98+ for _ in range (MAX_RETRIES ):
4799 ret = fetch_configlet ()
48100 if ret == 0 :
49101 break
0 commit comments