2021-10-05 09:44:12 -03:00

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)