Skip to content

Commit 6ad39cd

Browse files
committed
install packages from host machine instead of from containers
1 parent d04f22c commit 6ad39cd

File tree

2 files changed

+98
-7
lines changed

2 files changed

+98
-7
lines changed

tutor/commands/jobs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ def pip_install(context: click.Context, package: str) -> t.Iterable[tuple[str, s
573573
yield ("lms", script)
574574

575575

576-
@click.command(help="Remove an Xblock")
576+
@click.command(help="Remove a pip package at runtime")
577577
@click.pass_obj
578578
@click.pass_context
579579
@click.argument("package")
@@ -598,7 +598,7 @@ def pip_uninstall(
598598

599599
script = "rm -rf /mnt/persistent-python-packages/lib/"
600600
config = tutor_config.load(context.root)
601-
values = config["PERSISTENT_PIP_PACKAGES"]
601+
values = t.cast(list[str], config["PERSISTENT_PIP_PACKAGES"])
602602
remaining_packages = " ".join(values)
603603
if len(values) > 0:
604604
script += f" && pip install --prefix=/mnt/persistent-python-packages {remaining_packages}"

tutor/commands/packages.py

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import click
2+
import os
3+
import shlex
4+
import shutil
5+
import subprocess
6+
from typing import cast
27

38
from tutor import config as tutor_config
4-
from tutor import fmt
59
from tutor.commands.context import Context
10+
from tutor.commands.config import save as config_save_command
611

712

13+
HERE = os.path.abspath(os.path.dirname(__file__))
14+
DEPS_ZIP_PATH = os.path.join(HERE, "deps.zip")
15+
DEPS_PATH = os.path.join(HERE, "deps/")
16+
817
@click.group(
918
name="packages",
1019
short_help="Manage packages",
1120
)
1221
def packages_command() -> None:
13-
""" """
14-
22+
"""Custom persistent package manager for Tutor."""
1523

1624
@click.command(name="list")
1725
@click.pass_obj
@@ -21,10 +29,93 @@ def list_command(context: Context) -> None:
2129
2230
Entries will be fetched from the `PERSISTENT_PIP_PACKAGES` config setting.
2331
"""
32+
config = tutor_config.load(context.root)
33+
packages = [package for package in cast(list[str], config["PERSISTENT_PIP_PACKAGES"])]
34+
print(packages)
2435

36+
37+
@click.command(name="build")
38+
@click.pass_obj
39+
def build(context: Context) -> None:
40+
"""Rebuild dependencies from scratch."""
2541
config = tutor_config.load(context.root)
26-
packages = [package for package in config["PERSISTENT_PIP_PACKAGES"]]
27-
fmt.echo(sorted(packages))
42+
DEPS = cast(list[str], config["PERSISTENT_PIP_PACKAGES"])
43+
44+
build_dir = f"{context.root}/data/persistent-python-packages"
45+
46+
lib_path = os.path.join(build_dir, "lib")
47+
bin_path = os.path.join(build_dir, "bin")
48+
49+
if os.path.exists(lib_path):
50+
shutil.rmtree(lib_path)
51+
if os.path.exists(bin_path):
52+
shutil.rmtree(bin_path)
53+
54+
_pip_install(DEPS, build_dir)
55+
56+
57+
@click.command(name="append")
58+
@click.pass_obj
59+
@click.pass_context
60+
@click.argument("package")
61+
def append(click_context: click.Context, context: Context, package: str) -> None:
62+
"""Append a new package to the list."""
63+
click_context.invoke(
64+
config_save_command,
65+
interactive=False,
66+
set_vars=[],
67+
append_vars=[("PERSISTENT_PIP_PACKAGES", package)],
68+
remove_vars=[],
69+
unset_vars=[],
70+
env_only=False,
71+
clean_env=False,
72+
)
73+
74+
build_dir = f"{context.root}/data/persistent-python-packages"
75+
76+
if os.path.exists(DEPS_ZIP_PATH):
77+
shutil.unpack_archive(DEPS_ZIP_PATH, extract_dir=build_dir)
78+
79+
_pip_install([package], build_dir)
80+
81+
82+
@click.command(name="remove")
83+
@click.pass_context
84+
@click.argument("package")
85+
def remove(context: click.Context, package: str) -> None:
86+
"""Remove a package and rebuild archive from scratch."""
87+
context.invoke(
88+
config_save_command,
89+
interactive=False,
90+
set_vars=[],
91+
append_vars=[],
92+
remove_vars=[("PERSISTENT_PIP_PACKAGES", package)],
93+
unset_vars=[],
94+
env_only=False,
95+
clean_env=False,
96+
)
97+
98+
context.invoke(build)
99+
100+
101+
def _pip_install(deps: list[str], prefix_dir: str) -> None:
102+
for dep in deps:
103+
# We use python3.11 because that's whats used in the Dockerfile
104+
check_call("python3.11", "-m", "pip", "install", "--no-deps", f"--prefix={prefix_dir}", dep)
105+
check_call(f'touch "{prefix_dir}/.uwsgi_trigger"', shell=True)
106+
107+
108+
def check_call(*args: str, shell: bool = False) -> None:
109+
if shell:
110+
command = " ".join(args)
111+
print(command)
112+
subprocess.check_call(command, shell=True)
113+
else:
114+
print(shlex.join(args))
115+
subprocess.check_call(args)
28116

29117

30118
packages_command.add_command(list_command)
119+
packages_command.add_command(build)
120+
packages_command.add_command(append)
121+
packages_command.add_command(remove)

0 commit comments

Comments
 (0)