|
| 1 | +import argparse |
| 2 | +import pathlib |
| 3 | +import random |
| 4 | +import shutil |
| 5 | +import string |
1 | 6 | import subprocess |
| 7 | +from typing import Optional |
2 | 8 |
|
3 | | -import compas_rhino |
| 9 | +import compas |
4 | 10 |
|
| 11 | +executable = "python" if compas.WINDOWS else "python3.9" |
| 12 | +rhinocode = pathlib.Path().home() / ".rhinocode" |
| 13 | +rhinopython = rhinocode / "py39-rh8" / executable |
| 14 | +site_envs = rhinocode / "py39-rh8" / "site-envs" |
5 | 15 |
|
6 | | -def install(args): |
7 | | - subprocess.check_call([compas_rhino._get_default_rhino_cpython_path("8.0"), "-m", "pip", "install"] + args) |
8 | 16 |
|
| 17 | +def random_string(length=8) -> str: |
| 18 | + return "".join(random.choices(string.ascii_letters + string.digits, k=length)) |
9 | 19 |
|
10 | | -if __name__ == "__main__": |
11 | | - import argparse |
12 | 20 |
|
13 | | - parser = argparse.ArgumentParser() |
| 21 | +def find_full_env_name(name: str) -> str: |
| 22 | + for filepath in site_envs.iterdir(): |
| 23 | + if filepath.stem.startswith(name): |
| 24 | + return filepath.stem |
| 25 | + raise ValueError(f"No environment with this name exists: {name}") |
| 26 | + |
| 27 | + |
| 28 | +def ensure_site_env(name: str) -> str: |
| 29 | + try: |
| 30 | + fullname = find_full_env_name(name) |
| 31 | + except ValueError: |
| 32 | + fullname = f"{name}-{random_string()}" |
| 33 | + return fullname |
| 34 | + |
| 35 | + |
| 36 | +def install_in_rhino_with_pip( |
| 37 | + packages: list[str], |
| 38 | + requirements: Optional[str] = None, |
| 39 | + env: str = "default", |
| 40 | + upgrade: bool = False, |
| 41 | + deps: bool = True, |
| 42 | + clear: bool = False, |
| 43 | +): |
| 44 | + """Install a package with Rhino's CPython pip. |
| 45 | +
|
| 46 | + Parameters |
| 47 | + ---------- |
| 48 | + packages : list of str |
| 49 | + The package(s) to install (PyPI names or local package paths). |
| 50 | + requirements : str, optional |
| 51 | + Path to a requirements file. |
| 52 | + env : str, optional |
| 53 | + Name of the site env, without the random suffix. |
| 54 | + upgrade : bool, optional |
| 55 | + Attempt to upgrade packages already installed. |
| 56 | + deps : bool, optional |
| 57 | + If False, do not install dependencies. |
| 58 | + clear : bool, optional |
| 59 | + If True, clear the environment before installing. |
| 60 | +
|
| 61 | + Returns |
| 62 | + ------- |
| 63 | + int |
| 64 | + The return code from the pip install command. |
| 65 | +
|
| 66 | + Raises |
| 67 | + ------ |
| 68 | + ValueError |
| 69 | + If both packages and requirements are provided, or if neither are provided. |
| 70 | +
|
| 71 | + Examples |
| 72 | + -------- |
| 73 | + >>> install_in_rhino_with_pip(packages=["requests"], env="myenv", upgrade=True) |
| 74 | + >>> install_in_rhino_with_pip(requirements="requirements.txt", env="myenv") |
| 75 | + >>> install_in_rhino_with_pip(packages=["."], env="myenv") |
| 76 | + >>> install_in_rhino_with_pip(packages=[".."], env="myenv") |
| 77 | +
|
| 78 | + Notes |
| 79 | + ----- |
| 80 | + This function is made available as a command line script under the name `install_in_rhino`. |
| 81 | + On the command line you can use the following syntax |
| 82 | +
|
| 83 | + .. code-block:: bash |
| 84 | +
|
| 85 | + install_in_rhino requests numpy |
| 86 | + install_in_rhino -r requirements.txt --env myenv --upgrade |
| 87 | + install_in_rhino . --env myenv |
| 88 | + install_in_rhino .. --env myenv |
| 89 | + install_in_rhino -r requirements.txt --env myenv --no-deps |
| 90 | + install_in_rhino requests --env myenv --clear |
| 91 | +
|
| 92 | + """ |
14 | 93 |
|
15 | | - parser.add_argument("pipargs", help="Arguments to be passed on to pip as a string") |
| 94 | + if requirements and packages: |
| 95 | + raise ValueError("You must provide either packages or a requirements file, not both.") |
16 | 96 |
|
| 97 | + if requirements: |
| 98 | + params = ["-r", str(pathlib.Path(requirements).resolve())] |
| 99 | + elif packages: |
| 100 | + params = [] |
| 101 | + for p in packages: |
| 102 | + if p == ".": |
| 103 | + p = str(pathlib.Path().cwd()) |
| 104 | + elif p == "..": |
| 105 | + p = str(pathlib.Path().cwd().parent) |
| 106 | + params.append(p) |
| 107 | + else: |
| 108 | + raise ValueError("You must provide either packages or a requirements file.") |
| 109 | + |
| 110 | + env = env or "default" |
| 111 | + |
| 112 | + if clear: |
| 113 | + try: |
| 114 | + fullname = find_full_env_name(env) |
| 115 | + except ValueError: |
| 116 | + pass |
| 117 | + else: |
| 118 | + target = site_envs / fullname |
| 119 | + shutil.rmtree(target, ignore_errors=True) |
| 120 | + |
| 121 | + target = site_envs / ensure_site_env(env) |
| 122 | + target.mkdir(exist_ok=True) |
| 123 | + |
| 124 | + args = [ |
| 125 | + str(rhinopython), |
| 126 | + "-m", |
| 127 | + "pip", |
| 128 | + "install", |
| 129 | + *params, |
| 130 | + "--target", |
| 131 | + str(target), |
| 132 | + "--no-warn-script-location", |
| 133 | + ] |
| 134 | + |
| 135 | + if upgrade: |
| 136 | + args.append("--upgrade") |
| 137 | + if not deps: |
| 138 | + args.append("--no-deps") |
| 139 | + |
| 140 | + return subprocess.check_call(args) |
| 141 | + |
| 142 | + |
| 143 | +def main(): |
| 144 | + parser = argparse.ArgumentParser(description="Install a package with Rhino's CPython pip.") |
| 145 | + parser.add_argument("packages", help="The package(s) to install (PyPI names or local package paths)", nargs="*") |
| 146 | + parser.add_argument("-r", "--requirements", help="Path to a requirements file") |
| 147 | + parser.add_argument("--env", default="default", help="Name of the site env, without the random suffix") |
| 148 | + parser.add_argument("--upgrade", action="store_true", help="Attempt to upgrade packages already installed") |
| 149 | + parser.add_argument("--no-deps", dest="deps", action="store_false", help="Do not install dependencies") |
| 150 | + parser.add_argument("--clear", action="store_true", help="Clear the environment before installing") |
| 151 | + parser.set_defaults(deps=True) |
17 | 152 | args = parser.parse_args() |
18 | | - pipargs = args.pipargs.split() |
19 | 153 |
|
20 | | - install(pipargs) |
| 154 | + install_in_rhino_with_pip( |
| 155 | + packages=args.packages, |
| 156 | + requirements=args.requirements, |
| 157 | + env=args.env, |
| 158 | + upgrade=args.upgrade, |
| 159 | + deps=args.deps, |
| 160 | + clear=args.clear, |
| 161 | + ) |
| 162 | + |
| 163 | + |
| 164 | +# ============================================================================= |
| 165 | +# Main |
| 166 | +# ============================================================================= |
| 167 | + |
| 168 | +if __name__ == "__main__": |
| 169 | + main() |
0 commit comments