in scripts/generate_type_stubs_and_docs.py [0:0]
def main():
parser = argparse.ArgumentParser(
description="Generate type stub files (.pyi) and Sphinx documentation for Pedalboard."
)
parser.add_argument(
"--docs-output-dir", default="docs", help="Output directory for documentation HTML files."
)
parser.add_argument(
"--docs-input-dir",
default=os.path.join("docs", "source"),
help="Input directory for Sphinx.",
)
parser.add_argument(
"--skip-regenerating-type-hints",
action="store_true",
help="If set, don't bother regenerating or reprocessing type hint files.",
)
parser.add_argument(
"--check",
action="store_true",
help=(
"If set, compare the existing files with those that would have been generated if this"
" script were re-run."
),
)
parser.add_argument(
"--skip-comparing",
nargs="*",
default=["*.js", "*.css"],
help=(
"If set and if --check is passed, the provided filenames (including '*' globs) will be"
" ignored when comparing expected file contents against actual file contents."
),
)
args = parser.parse_args()
patch_mypy_stubtest()
patch_pybind11_stubgen()
patch_sphinx_to_read_pyi()
if not args.skip_regenerating_type_hints:
with isolated_imports(
{
"pedalboard",
"pedalboard.io",
"pedalboard_native",
"pedalboard_native.io",
"pedalboard_native.utils",
}
):
# print("Generating type stubs from pure-Python code...")
# subprocess.check_call(["stubgen", "-o", tempdir, "pedalboard/_pedalboard.py"])
# Generate .pyi stubs files from Pedalboard's native (Pybind11) source.
# Documentation will be copied from the Pybind11 docstrings, and these stubs
# files will be used as the "source of truth" for both IDE autocompletion
# and Sphinx documentation.
print("Generating type stubs from native code...")
pybind11_stubgen_main(["-o", "pedalboard_native", "pedalboard_native", "--no-setup-py"])
# Move type hints out of pedalboard_native/something_else/*:
native_dir = pathlib.Path("pedalboard_native")
native_subdir = [f for f in native_dir.glob("*") if "stubs" in f.name][0]
shutil.copytree(native_subdir, native_dir, dirs_exist_ok=True)
shutil.rmtree(native_subdir)
# Post-process the type hints generaetd by pybind11_stubgen; we can't patch
# everything easily, so we do string manipulation after-the-fact and run ``black``.
print("Postprocessing generated type hints...")
postprocess_type_hints_main(
["pedalboard_native", "pedalboard_native"] + (["--check"] if args.check else [])
)
# Run mypy.stubtest to ensure that the results are correct and nothing got missed:
if sys.version_info > (3, 6):
print("Running `mypy.stubtest` to validate stubs match...")
test_stubs(
mypy_parse_options(
[
"pedalboard",
"--allowlist",
"stubtest.allowlist",
"--ignore-missing-stub",
"--ignore-unused-allowlist",
]
)
)
# Re-run this same script in a fresh interpreter, but with skip_regenerating_type_hints
# enabled:
subprocess.check_call(
[psutil.Process(os.getpid()).exe()] + sys.argv + ["--skip-regenerating-type-hints"]
)
return
# Why is this necessary? I don't know, but without it, things fail.
print("Importing numpy to ensure a successful Pedalboard stub import...")
import numpy # noqa
print("Importing .pyi files for our native modules...")
for modname in ["pedalboard_native", "pedalboard_native.io", "pedalboard_native.utils"]:
import_stub(".", modname)
print("Running Sphinx...")
if args.check:
missing_files = []
mismatched_files = []
with TemporaryDirectory() as tempdir:
sphinx_build_main(["-b", "html", args.docs_input_dir, tempdir, "-v", "-v", "-v"])
postprocess_sphinx_output(tempdir, SPHINX_REPLACEMENTS)
remove_non_public_files(tempdir)
for dirpath, _dirnames, filenames in os.walk(tempdir):
prefix = dirpath.replace(tempdir, "").lstrip(os.path.sep)
for filename in filenames:
if glob_matches(filename, args.skip_comparing):
print(f"Skipping comparison of file: {filename}")
continue
expected_path = os.path.join(tempdir, prefix, filename)
actual_path = os.path.join(args.docs_output_dir, prefix, filename)
if not os.path.isfile(actual_path):
missing_files.append(os.path.join(prefix, filename))
else:
with open(expected_path, "rb") as e, open(actual_path, "rb") as a:
if e.read() != a.read():
mismatched_files.append(os.path.join(prefix, filename))
if missing_files or mismatched_files:
error_lines = []
if missing_files:
error_lines.append(
f"{len(missing_files):,} file(s) were expected in {args.docs_output_dir},"
" but not found:"
)
for missing_file in missing_files:
error_lines.append(f"\t{missing_file}")
if mismatched_files:
error_lines.append(
f"{len(mismatched_files):,} file(s) in {args.docs_output_dir} did not match"
" expected values:"
)
for mismatched_file in mismatched_files:
expected_path = os.path.join(tempdir, mismatched_file)
actual_path = os.path.join(args.docs_output_dir, mismatched_file)
try:
with open(expected_path) as e, open(actual_path) as a:
diff = difflib.context_diff(
e.readlines(),
a.readlines(),
os.path.join("expected", mismatched_file),
os.path.join("actual", mismatched_file),
)
error_lines.append("\n".join([trim_diff_line(x) for x in diff]))
except UnicodeDecodeError:
error_lines.append(
f"Binary file {mismatched_file} does not match expected contents."
)
raise ValueError("\n".join(error_lines))
print("Done! Generated type stubs and documentation are valid.")
else:
sphinx_build_main(["-b", "html", args.docs_input_dir, args.docs_output_dir])
postprocess_sphinx_output(args.docs_output_dir, SPHINX_REPLACEMENTS)
remove_non_public_files(args.docs_output_dir)
print(f"Done! Commit the contents of `{args.docs_output_dir}` to Git.")