Added majority of type annotations (to a by now outdated version)

This commit is contained in:
Yannick Jadoul
2020-05-06 23:55:54 +02:00
parent 185a1c09ad
commit 618f1cefae
7 changed files with 110 additions and 74 deletions
+22 -13
View File
@@ -5,6 +5,8 @@ import textwrap
import traceback
from configparser import ConfigParser
from typing import Any, Dict, List, Optional, Union, overload
import cibuildwheel
import cibuildwheel.linux
import cibuildwheel.macos
@@ -14,14 +16,20 @@ from cibuildwheel.environment import (
parse_environment,
)
from cibuildwheel.util import (
BuildOptions,
BuildSelector,
DependencyConstraints,
Unbuffered,
BuildOptions
)
def get_option_from_environment(option_name, platform=None, default=None):
@overload
def get_option_from_environment(option_name: str, platform: Optional[str], default: str) -> str:
...
@overload
def get_option_from_environment(option_name: str, platform: Optional[str] = None, default: None = None) -> Optional[str]:
...
def get_option_from_environment(option_name: str, platform: Optional[str] = None, default: Optional[str] = None) -> Optional[str]:
'''
Returns an option from the environment, optionally scoped by the platform.
@@ -39,13 +47,13 @@ def get_option_from_environment(option_name, platform=None, default=None):
return os.environ.get(option_name, default)
def strtobool(val):
def strtobool(val: str) -> bool:
if val.lower() in ('y', 'yes', 't', 'true', 'on', '1'):
return True
return False
def main():
def main() -> None:
parser = argparse.ArgumentParser(
description='Build wheels for all the platforms.',
epilog='''
@@ -114,7 +122,7 @@ def main():
test_extras = get_option_from_environment('CIBW_TEST_EXTRAS', platform=platform, default='')
package_dir = args.package_dir
before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform)
build_verbosity = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
build_verbosity_str = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '')
if platform == 'linux':
repair_command_default = 'auditwheel repair -w {dest_dir} {wheel}'
@@ -138,7 +146,7 @@ def main():
test_extras = '[{0}]'.format(test_extras)
try:
build_verbosity = min(3, max(-3, int(build_verbosity)))
build_verbosity = min(3, max(-3, int(build_verbosity_str)))
except ValueError:
build_verbosity = 0
@@ -175,7 +183,8 @@ def main():
# 'pypy_x86_64': {'manylinux2010': '...' }
# ... }
manylinux_images = {}
manylinux_images = {} # type: Optional[Dict[str, str]]
assert manylinux_images is not None # Weird problem with mypy
for build_platform in ['x86_64', 'i686', 'pypy_x86_64', 'aarch64', 'ppc64le', 's390x']:
pinned_images = all_pinned_docker_images[build_platform]
@@ -213,7 +222,7 @@ def main():
)
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
sys.stdout = Unbuffered(sys.stdout)
sys.stdout = Unbuffered(sys.stdout) # type: ignore
print_preamble(platform, build_options)
@@ -231,7 +240,7 @@ def main():
exit(2)
def detect_obsolete_options():
def detect_obsolete_options() -> None:
# Check the old 'MANYLINUX1_*_IMAGE' options
for (deprecated, alternative) in [('CIBW_MANYLINUX1_X86_64_IMAGE', 'CIBW_MANYLINUX_X86_64_IMAGE'),
('CIBW_MANYLINUX1_I686_IMAGE', 'CIBW_MANYLINUX_I686_IMAGE')]:
@@ -258,7 +267,7 @@ def detect_obsolete_options():
os.environ[option] = os.environ[option].replace(deprecated, alternative)
def print_preamble(platform, build_options):
def print_preamble(platform: str, build_options: BuildOptions) -> None:
print(textwrap.dedent('''
_ _ _ _ _ _ _
___|_| |_ _ _|_| |_| |_ _ _| |_ ___ ___| |
@@ -282,9 +291,9 @@ def print_preamble(platform, build_options):
print('\nHere we go!\n')
def print_build_identifiers(platform, build_selector):
def print_build_identifiers(platform: str, build_selector: BuildSelector) -> None:
if platform == 'linux':
python_configurations = cibuildwheel.linux.get_python_configurations(build_selector)
python_configurations = cibuildwheel.linux.get_python_configurations(build_selector) # type: List[Any]
elif platform == 'windows':
python_configurations = cibuildwheel.windows.get_python_configurations(build_selector)
elif platform == 'macos':
@@ -296,7 +305,7 @@ def print_build_identifiers(platform, build_selector):
print(config.identifier)
def detect_warnings(platform, build_options):
def detect_warnings(platform: str, build_options: BuildOptions) -> List[str]:
warnings = []
# warn about deprecated {python} and {pip}
+14 -12
View File
@@ -2,12 +2,14 @@ import shlex
import subprocess
from collections import namedtuple
import bashlex
from typing import Dict
import bashlex # type: ignore
NodeExecutionContext = namedtuple('NodeExecutionContext', ['environment', 'input'])
def evaluate(value, environment):
def evaluate(value: str, environment: Dict[str, str]) -> str:
if not value:
# empty string evaluates to empty string
# (but trips up bashlex)
@@ -20,24 +22,24 @@ def evaluate(value, environment):
value_word_node = command_node.parts[0]
return evaluate_node(
return evaluate_node( # type: ignore
value_word_node,
context=NodeExecutionContext(environment=environment, input=value)
)
def evaluate_node(node, context):
def evaluate_node(node, context): # type: ignore
if node.kind == 'word':
return evaluate_word_node(node, context=context)
return evaluate_word_node(node, context=context) # type: ignore
elif node.kind == 'commandsubstitution':
return evaluate_command_node(node.command, context=context)
return evaluate_command_node(node.command, context=context) # type: ignore
elif node.kind == 'parameter':
return evaluate_parameter_node(node, context=context)
return evaluate_parameter_node(node, context=context) # type: ignore
else:
raise ValueError('Unsupported bash construct: "%s"' % node.word)
def evaluate_word_node(node, context):
def evaluate_word_node(node, context): # type: ignore
word_start = node.pos[0]
word_end = node.pos[1]
word_string = context.input[word_start:word_end]
@@ -51,7 +53,7 @@ def evaluate_word_node(node, context):
for i in range(part_start, part_end):
letters[i] = None
letters[part_start] = evaluate_node(part, context=context)
letters[part_start] = evaluate_node(part, context=context) # type: ignore
# remove the None letters and concat
value = ''.join(l for l in letters if l is not None)
@@ -60,11 +62,11 @@ def evaluate_word_node(node, context):
return ' '.join(word.strip() for word in shlex.split(value))
def evaluate_command_node(node, context):
words = [evaluate_node(part, context=context) for part in node.parts]
def evaluate_command_node(node, context): # type: ignore
words = [evaluate_node(part, context=context) for part in node.parts] # type: ignore
command = ' '.join(words)
return subprocess.check_output(shlex.split(command), env=context.environment, universal_newlines=True)
def evaluate_parameter_node(node, context):
def evaluate_parameter_node(node, context): # type: ignore
return context.environment.get(node.value, '')
+18 -16
View File
@@ -1,4 +1,6 @@
import bashlex
import bashlex # type: ignore
from typing import Dict, List
from . import bashlex_eval
@@ -7,13 +9,7 @@ class EnvironmentParseError(Exception):
pass
def parse_environment(env_string):
env_items = split_env_items(env_string)
assignments = [EnvironmentAssignment(item) for item in env_items]
return ParsedEnvironment(assignments=assignments)
def split_env_items(env_string):
def split_env_items(env_string: str) -> List[str]:
'''Splits space-separated variable assignments into a list of individual assignments.
>>> split_env_items('VAR=abc')
@@ -43,29 +39,29 @@ def split_env_items(env_string):
class EnvironmentAssignment:
def __init__(self, assignment):
def __init__(self, assignment: str):
name, equals, value = assignment.partition('=')
if not equals:
raise EnvironmentParseError(assignment)
self.name = name
self.value = value
def evaluated_value(self, environment):
def evaluated_value(self, environment: Dict[str, str]) -> str:
'''Returns the value of this assignment, as evaluated in the environment'''
return bashlex_eval.evaluate(self.value, environment=environment)
def as_shell_assignment(self):
def as_shell_assignment(self) -> str:
return 'export %s=%s' % (self.name, self.value)
def __repr__(self):
def __repr__(self) -> str:
return '%s=%s' % (self.name, self.value)
class ParsedEnvironment:
def __init__(self, assignments):
def __init__(self, assignments: List[EnvironmentAssignment]):
self.assignments = assignments
def as_dictionary(self, prev_environment):
def as_dictionary(self, prev_environment: Dict[str, str]) -> Dict[str, str]:
environment = prev_environment.copy()
for assignment in self.assignments:
@@ -74,8 +70,14 @@ class ParsedEnvironment:
return environment
def as_shell_commands(self):
def as_shell_commands(self) -> List[str]:
return [a.as_shell_assignment() for a in self.assignments]
def __repr__(self):
def __repr__(self) -> str:
return 'ParsedEnvironment(%r)' % [repr(a) for a in self.assignments]
def parse_environment(env_string: str) -> ParsedEnvironment:
env_items = split_env_items(env_string)
assignments = [EnvironmentAssignment(item) for item in env_items]
return ParsedEnvironment(assignments=assignments)
+12 -4
View File
@@ -7,10 +7,15 @@ import textwrap
import uuid
from collections import namedtuple
from typing import Callable, Dict, List, Optional
from .environment import (
ParsedEnvironment,
)
from .util import (
BuildOptions,
get_build_verbosity_extra_flags,
prepare_command,
BuildOptions
)
@@ -42,8 +47,10 @@ def matches_platform(identifier):
return False
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'path'])
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'path'])
def get_python_configurations(build_selector: Callable[[str], bool]) -> List[PythonConfiguration]:
python_configurations = [
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27m'),
PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path='/opt/python/cp27-cp27mu'),
@@ -86,6 +93,7 @@ def build(options: BuildOptions):
file=sys.stderr)
exit(2)
assert options.manylinux_images is not None
python_configurations = get_python_configurations(options.build_selector)
platforms = [
('cp', 'manylinux_x86_64', options.manylinux_images['x86_64']),
@@ -274,7 +282,7 @@ def build(options: BuildOptions):
call(['docker', 'rm', '--force', '-v', container_name])
def troubleshoot(package_dir, error):
def troubleshoot(package_dir: str, error: Exception) -> None:
if (isinstance(error, subprocess.CalledProcessError) and 'exec' in error.cmd):
# the bash script failed
print('Checking for common errors...')
+15 -8
View File
@@ -7,16 +7,21 @@ import tempfile
from collections import namedtuple
from glob import glob
from typing import Callable, Dict, List, Optional, Union
from .environment import (
ParsedEnvironment,
)
from .util import (
BuildOptions,
download,
get_build_verbosity_extra_flags,
prepare_command,
get_pip_script,
BuildOptions
prepare_command,
)
def call(args, env=None, cwd=None, shell=False):
def call(args: Union[str, List[str]], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None, shell: bool = False) -> int:
# print the command executing for the logs
if shell:
print('+ %s' % args)
@@ -26,8 +31,10 @@ def call(args, env=None, cwd=None, shell=False):
return subprocess.check_call(args, env=env, cwd=cwd, shell=shell)
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'identifier', 'url'])
def get_python_configurations(build_selector: Callable[[str], bool]) -> List[PythonConfiguration]:
python_configurations = [
# CPython
PythonConfiguration(version='2.7', identifier='cp27-macosx_x86_64', url='https://www.python.org/ftp/python/2.7.18/python-2.7.18-macosx10.9.pkg'),
@@ -47,7 +54,7 @@ def get_python_configurations(build_selector):
SYMLINKS_DIR = '/tmp/cibw_bin'
def make_symlinks(installation_bin_path, python_executable, pip_executable):
def make_symlinks(installation_bin_path: str, python_executable: str, pip_executable: str) -> None:
assert os.path.exists(os.path.join(installation_bin_path, python_executable))
# Python bin folders on Mac don't symlink `python3` to `python`, and neither
@@ -62,7 +69,7 @@ def make_symlinks(installation_bin_path, python_executable, pip_executable):
os.symlink(os.path.join(installation_bin_path, pip_executable), os.path.join(SYMLINKS_DIR, 'pip'))
def install_cpython(version, url):
def install_cpython(version: str, url: str) -> str:
installed_system_packages = subprocess.check_output(['pkgutil', '--pkgs'], universal_newlines=True).splitlines()
# if this version of python isn't installed, get it from python.org and install
@@ -86,7 +93,7 @@ def install_cpython(version, url):
return installation_bin_path
def install_pypy(version, url):
def install_pypy(version: str, url: str) -> str:
pypy_tar_bz2 = url.rsplit('/', 1)[-1]
assert pypy_tar_bz2.endswith(".tar.bz2")
pypy_base_filename = os.path.splitext(os.path.splitext(pypy_tar_bz2)[0])[0]
+13 -12
View File
@@ -2,12 +2,13 @@ import os
import urllib.request
from fnmatch import fnmatch
from time import sleep
from typing import NamedTuple, List, Optional, Dict
from typing import Dict, List, NamedTuple, Optional
from .environment import ParsedEnvironment
def prepare_command(command, **kwargs):
def prepare_command(command: str, **kwargs: str) -> str:
'''
Preprocesses a command by expanding variables like {python}.
@@ -17,7 +18,7 @@ def prepare_command(command, **kwargs):
return command.format(python='python', pip='pip', **kwargs)
def get_build_verbosity_extra_flags(level):
def get_build_verbosity_extra_flags(level: int) -> List[str]:
if level > 0:
return ['-' + level * 'v']
elif level < 0:
@@ -27,37 +28,37 @@ def get_build_verbosity_extra_flags(level):
class BuildSelector:
def __init__(self, build_config, skip_config):
def __init__(self, build_config: str, skip_config: str):
self.build_patterns = build_config.split()
self.skip_patterns = skip_config.split()
def __call__(self, build_id):
def match_any(patterns):
def __call__(self, build_id: str) -> bool:
def match_any(patterns: List[str]) -> bool:
return any(fnmatch(build_id, pattern) for pattern in patterns)
return match_any(self.build_patterns) and not match_any(self.skip_patterns)
def __repr__(self):
def __repr__(self) -> str:
return 'BuildSelector({!r} - {!r})'.format(' '.join(self.build_patterns), ' '.join(self.skip_patterns))
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
def __init__(self, stream):
def __init__(self, stream): # type: ignore
self.stream = stream
def write(self, data):
def write(self, data): # type: ignore
self.stream.write(data)
self.stream.flush()
def writelines(self, datas):
def writelines(self, datas): # type: ignore
self.stream.writelines(datas)
self.stream.flush()
def __getattr__(self, attr):
def __getattr__(self, attr): # type: ignore
return getattr(self.stream, attr)
def download(url, dest):
def download(url: str, dest: str) -> None:
print('+ Download ' + url + ' to ' + dest)
dest_dir = os.path.dirname(dest)
if not os.path.exists(dest_dir):
+16 -9
View File
@@ -7,12 +7,17 @@ from collections import namedtuple
from glob import glob
from zipfile import ZipFile
from typing import Callable, Dict, List, Optional
from .environment import (
ParsedEnvironment,
)
from .util import (
BuildOptions,
download,
get_build_verbosity_extra_flags,
prepare_command,
get_pip_script,
BuildOptions
prepare_command,
)
@@ -20,20 +25,22 @@ IS_RUNNING_ON_AZURE = os.path.exists('C:\\hostedtoolcache')
IS_RUNNING_ON_TRAVIS = os.environ.get('TRAVIS_OS_NAME') == 'windows'
def shell(args, env=None, cwd=None):
def shell(args: List[str], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None) -> int:
print('+ ' + ' '.join(args))
return subprocess.check_call(' '.join(args), env=env, cwd=cwd, shell=True)
def get_nuget_args(version, arch):
def get_nuget_args(version: str, arch: str) -> List[str]:
python_name = 'python' if version[0] == '3' else 'python2'
if arch == '32':
python_name = python_name + 'x86'
return [python_name, '-Version', version, '-OutputDirectory', 'C:\\cibw\\python']
def get_python_configurations(build_selector):
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url'])
PythonConfiguration = namedtuple('PythonConfiguration', ['version', 'arch', 'identifier', 'url'])
def get_python_configurations(build_selector: Callable[[str], bool]) -> List[PythonConfiguration]:
python_configurations = [
# CPython
PythonConfiguration(version='2.7.18', arch='32', identifier='cp27-win32', url=None),
@@ -62,19 +69,19 @@ def get_python_configurations(build_selector):
return python_configurations
def extract_zip(zip_src, dest):
def extract_zip(zip_src: str, dest: str) -> None:
with ZipFile(zip_src) as zip:
zip.extractall(dest)
def install_cpython(version, arch, nuget):
def install_cpython(version: str, arch: str, nuget: str) -> str:
nuget_args = get_nuget_args(version, arch)
installation_path = os.path.join(nuget_args[-1], nuget_args[0] + '.' + version, 'tools')
shell([nuget, 'install'] + nuget_args)
return installation_path
def install_pypy(version, arch, url):
def install_pypy(version: str, arch: str, url: str) -> str:
assert arch == '32'
# Inside the PyPy zip file is a directory with the same name
zip_filename = url.rsplit('/', 1)[-1]