156 lines
5.5 KiB
Python
156 lines
5.5 KiB
Python
"""Utilities for second set of flag examples.
|
|
"""
|
|
|
|
import argparse
|
|
import string
|
|
import sys
|
|
import time
|
|
from collections import Counter
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
|
|
DownloadStatus = Enum('DownloadStatus', 'OK NOT_FOUND ERROR')
|
|
|
|
POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
|
|
'MX PH VN ET EG DE IR TR CD FR').split()
|
|
|
|
DEFAULT_CONCUR_REQ = 1
|
|
MAX_CONCUR_REQ = 1
|
|
|
|
SERVERS = {
|
|
'REMOTE': 'https://www.fluentpython.com/data/flags',
|
|
'LOCAL': 'http://localhost:8000/flags',
|
|
'DELAY': 'http://localhost:8001/flags',
|
|
'ERROR': 'http://localhost:8002/flags',
|
|
}
|
|
DEFAULT_SERVER = 'LOCAL'
|
|
|
|
DEST_DIR = Path('downloaded')
|
|
COUNTRY_CODES_FILE = Path('country_codes.txt')
|
|
|
|
|
|
def save_flag(img: bytes, filename: str) -> None:
|
|
(DEST_DIR / filename).write_bytes(img)
|
|
|
|
|
|
def initial_report(cc_list: list[str],
|
|
actual_req: int,
|
|
server_label: str) -> None:
|
|
if len(cc_list) <= 10:
|
|
cc_msg = ', '.join(cc_list)
|
|
else:
|
|
cc_msg = f'from {cc_list[0]} to {cc_list[-1]}'
|
|
print(f'{server_label} site: {SERVERS[server_label]}')
|
|
plural = 's' if len(cc_list) != 1 else ''
|
|
print(f'Searching for {len(cc_list)} flag{plural}: {cc_msg}')
|
|
if actual_req == 1:
|
|
print('1 connection will be used.')
|
|
else:
|
|
print(f'{actual_req} concurrent connections will be used.')
|
|
|
|
|
|
def final_report(cc_list: list[str],
|
|
counter: Counter[DownloadStatus],
|
|
start_time: float) -> None:
|
|
elapsed = time.perf_counter() - start_time
|
|
print('-' * 20)
|
|
plural = 's' if counter[DownloadStatus.OK] != 1 else ''
|
|
print(f'{counter[DownloadStatus.OK]:3} flag{plural} downloaded.')
|
|
if counter[DownloadStatus.NOT_FOUND]:
|
|
print(f'{counter[DownloadStatus.NOT_FOUND]:3} not found.')
|
|
if counter[DownloadStatus.ERROR]:
|
|
plural = 's' if counter[DownloadStatus.ERROR] != 1 else ''
|
|
print(f'{counter[DownloadStatus.ERROR]:3} error{plural}.')
|
|
print(f'Elapsed time: {elapsed:.2f}s')
|
|
|
|
|
|
def expand_cc_args(every_cc: bool,
|
|
all_cc: bool,
|
|
cc_args: list[str],
|
|
limit: int) -> list[str]:
|
|
codes: set[str] = set()
|
|
A_Z = string.ascii_uppercase
|
|
if every_cc:
|
|
codes.update(a+b for a in A_Z for b in A_Z)
|
|
elif all_cc:
|
|
text = COUNTRY_CODES_FILE.read_text()
|
|
codes.update(text.split())
|
|
else:
|
|
for cc in (c.upper() for c in cc_args):
|
|
if len(cc) == 1 and cc in A_Z:
|
|
codes.update(cc + c for c in A_Z)
|
|
elif len(cc) == 2 and all(c in A_Z for c in cc):
|
|
codes.add(cc)
|
|
else:
|
|
raise ValueError('*** Usage error: each CC argument '
|
|
'must be A to Z or AA to ZZ.')
|
|
return sorted(codes)[:limit]
|
|
|
|
|
|
def process_args(default_concur_req):
|
|
server_options = ', '.join(sorted(SERVERS))
|
|
parser = argparse.ArgumentParser(
|
|
description='Download flags for country codes. '
|
|
'Default: top 20 countries by population.')
|
|
parser.add_argument(
|
|
'cc', metavar='CC', nargs='*',
|
|
help='country code or 1st letter (eg. B for BA...BZ)')
|
|
parser.add_argument(
|
|
'-a', '--all', action='store_true',
|
|
help='get all available flags (AD to ZW)')
|
|
parser.add_argument(
|
|
'-e', '--every', action='store_true',
|
|
help='get flags for every possible code (AA...ZZ)')
|
|
parser.add_argument(
|
|
'-l', '--limit', metavar='N', type=int, help='limit to N first codes',
|
|
default=sys.maxsize)
|
|
parser.add_argument(
|
|
'-m', '--max_req', metavar='CONCURRENT', type=int,
|
|
default=default_concur_req,
|
|
help=f'maximum concurrent requests (default={default_concur_req})')
|
|
parser.add_argument(
|
|
'-s', '--server', metavar='LABEL', default=DEFAULT_SERVER,
|
|
help=f'Server to hit; one of {server_options} '
|
|
f'(default={DEFAULT_SERVER})')
|
|
parser.add_argument(
|
|
'-v', '--verbose', action='store_true',
|
|
help='output detailed progress info')
|
|
args = parser.parse_args()
|
|
if args.max_req < 1:
|
|
print('*** Usage error: --max_req CONCURRENT must be >= 1')
|
|
parser.print_usage()
|
|
# "standard" exit status codes:
|
|
# https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/40484670#40484670
|
|
sys.exit(2) # command line usage error
|
|
if args.limit < 1:
|
|
print('*** Usage error: --limit N must be >= 1')
|
|
parser.print_usage()
|
|
sys.exit(2) # command line usage error
|
|
args.server = args.server.upper()
|
|
if args.server not in SERVERS:
|
|
print(f'*** Usage error: --server LABEL '
|
|
f'must be one of {server_options}')
|
|
parser.print_usage()
|
|
sys.exit(2) # command line usage error
|
|
try:
|
|
cc_list = expand_cc_args(args.every, args.all, args.cc, args.limit)
|
|
except ValueError as exc:
|
|
print(exc.args[0])
|
|
parser.print_usage()
|
|
sys.exit(2) # command line usage error
|
|
|
|
if not cc_list:
|
|
cc_list = sorted(POP20_CC)[:args.limit]
|
|
return args, cc_list
|
|
|
|
|
|
def main(download_many, default_concur_req, max_concur_req):
|
|
args, cc_list = process_args(default_concur_req)
|
|
actual_req = min(args.max_req, max_concur_req, len(cc_list))
|
|
initial_report(cc_list, actual_req, args.server)
|
|
base_url = SERVERS[args.server]
|
|
DEST_DIR.mkdir(exist_ok=True)
|
|
t0 = time.perf_counter()
|
|
counter = download_many(cc_list, base_url, args.verbose, actual_req)
|
|
final_report(cc_list, counter, t0)
|