Skip to content

Instantly share code, notes, and snippets.

@agoose77
Last active August 13, 2025 11:06
Show Gist options
  • Save agoose77/4e55006a3276c385f9bd71464bb00d08 to your computer and use it in GitHub Desktop.
Save agoose77/4e55006a3276c385f9bd71464bb00d08 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import re
import sys
import tomllib
import json
import argparse
import pathlib
import shutil
import subprocess
REGEX = r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$"
def parse_script_metadata(script: str) -> dict | None:
name = "script"
matches = list(
filter(lambda m: m.group("type") == name, re.finditer(REGEX, script))
)
if len(matches) > 1:
raise ValueError(f"Multiple {name} blocks found")
elif len(matches) == 1:
content = "".join(
line[2:] if line.startswith("# ") else line[1:]
for line in matches[0].group("content").splitlines(keepends=True)
)
return tomllib.loads(content)
else:
return None
def iter_nodes(mdast, type_):
if mdast["type"] == type_:
yield mdast
if "children" in mdast:
for child in mdast["children"]:
yield from iter_nodes(child, type_)
def parse_document_spec(content):
for node in iter_nodes(content["mdast"], "code"):
metadata = parse_script_metadata(node["value"])
if metadata is not None:
break
else:
return None
script_dependencies = metadata.get("dependencies", [])
return [*script_dependencies, "ipykernel"]
VENV_PATH = pathlib.Path(".myst-venvs").absolute()
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument("build_path", type=pathlib.Path)
parser.add_argument(
"-r", "--root-venv-path", type=pathlib.Path, default=pathlib.Path(sys.prefix)
)
args = parser.parse_args(argv)
envs_path = VENV_PATH
if envs_path.exists():
shutil.rmtree(envs_path)
root_venv_path = args.root_venv_path.absolute()
for p in args.build_path.absolute().glob("**/*.json"):
with open(p, "r") as f:
content = json.load(f)
kernel_name = content["frontmatter"].get("kernelspec", {}).get("name")
if kernel_name is None:
doc_name = content["location"].removeprefix("/")
print(f"Missing kernel name in {doc_name}")
continue
spec = parse_document_spec(content)
if spec is None:
continue
env_path = envs_path / kernel_name
env_path.mkdir(exist_ok=True, parents=True)
print(f"Provisioning environment for {p.name} at {env_path}")
build_environment(kernel_name, spec, env_path, root_venv_path)
def build_environment(kernel_name, spec, env_path, root_prefix):
subprocess.run([sys.executable, "-m", "venv", env_path], check=True)
interp_path = env_path / "bin" / "python"
subprocess.run([interp_path, "-m", "pip", "install", *spec, "-qqq"], check=True)
subprocess.run(
[
interp_path,
"-m",
"ipykernel",
"install",
"--name",
kernel_name,
"--prefix",
root_prefix,
],
check=True,
)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment