|
4 | 4 | This updated script resolves simple version conflicts by choosing the newest version seen |
5 | 5 | for a given package (and emits a pinned `pkg==<newest>`). It still preserves |
6 | 6 | non-package lines (pip options, VCS/URLs) and supports simple `-r` includes. |
| 7 | +
|
| 8 | +It also automatically adds ipywidgets if missing, with a version determined by |
| 9 | +Python, JupyterLab, and matplotlib versions from environment.yaml. |
7 | 10 | """ |
8 | 11 | from pathlib import Path |
9 | 12 | import argparse |
10 | 13 | import sys |
11 | 14 | from collections import OrderedDict |
12 | 15 | from datetime import datetime |
13 | 16 | import re |
| 17 | +import yaml |
14 | 18 |
|
15 | 19 |
|
16 | 20 | def normalize_line(line: str) -> str: |
@@ -129,6 +133,50 @@ def find_requirements_files(source_dir: Path): |
129 | 133 | return sorted(source_dir.rglob("requirements*.txt")) |
130 | 134 |
|
131 | 135 |
|
| 136 | +def read_environment_yaml(env_path: Path) -> dict: |
| 137 | + """Read environment.yaml and extract Python, JupyterLab, matplotlib versions.""" |
| 138 | + if not env_path.exists(): |
| 139 | + return {} |
| 140 | + try: |
| 141 | + data = yaml.safe_load(env_path.read_text(encoding="utf-8")) |
| 142 | + versions = {} |
| 143 | + for dep in data.get("dependencies", []): |
| 144 | + if isinstance(dep, str): |
| 145 | + if dep.startswith("python="): |
| 146 | + versions["python"] = dep.split("=")[-1] |
| 147 | + elif dep.startswith("jupyterlab="): |
| 148 | + versions["jupyterlab"] = dep.split("=")[-1] |
| 149 | + elif dep.startswith("matplotlib="): |
| 150 | + versions["matplotlib"] = dep.split("=")[-1] |
| 151 | + return versions |
| 152 | + except Exception: |
| 153 | + return {} |
| 154 | + |
| 155 | + |
| 156 | +def resolve_ipywidgets_version(env_versions: dict) -> str: |
| 157 | + """Resolve ipywidgets version based on Python, JupyterLab, matplotlib versions. |
| 158 | + |
| 159 | + Returns a version spec string like ">=7.0.0,<8.0.0" or a pinned version. |
| 160 | + """ |
| 161 | + py_ver = env_versions.get("python", "3.11") |
| 162 | + jl_ver = env_versions.get("jupyterlab", "4.0") |
| 163 | + mpl_ver = env_versions.get("matplotlib", "3.0") |
| 164 | + |
| 165 | + # Simple heuristic: ipywidgets 8.x for modern stacks, 7.x for older |
| 166 | + try: |
| 167 | + py_major, py_minor = map(int, py_ver.split(".")[:2]) |
| 168 | + jl_major = int(jl_ver.split(".")[0]) |
| 169 | + |
| 170 | + if jl_major >= 4 and py_major >= 3 and py_minor >= 9: |
| 171 | + return "8.1.1" # Latest stable for modern Python + JupyterLab 4 |
| 172 | + elif py_major >= 3 and py_minor >= 8: |
| 173 | + return "8.0.4" # Good for Python 3.8+ |
| 174 | + else: |
| 175 | + return "7.7.2" # Fallback for older environments |
| 176 | + except Exception: |
| 177 | + return ">=7.0.0" # Safe fallback |
| 178 | + |
| 179 | + |
132 | 180 | def main(): |
133 | 181 | parser = argparse.ArgumentParser(description="Merge multiple requirements.txt files into one.") |
134 | 182 | parser.add_argument("--source-dir", default="notebooks", help="Directory to scan for requirements files") |
@@ -163,6 +211,25 @@ def main(): |
163 | 211 | for ol in other_lines.keys(): |
164 | 212 | final_lines.append(ol) |
165 | 213 |
|
| 214 | + # Check if ipywidgets is already present |
| 215 | + has_ipywidgets = "ipywidgets" in pkgs |
| 216 | + |
| 217 | + # If not, resolve and add it based on environment.yaml |
| 218 | + if not has_ipywidgets: |
| 219 | + env_path = Path(args.env_output) if args.generate_env else Path("environment.yaml") |
| 220 | + env_versions = read_environment_yaml(env_path) |
| 221 | + ipyw_version = resolve_ipywidgets_version(env_versions) |
| 222 | + |
| 223 | + # Add to pkgs and pkg_order |
| 224 | + pkgs["ipywidgets"] = { |
| 225 | + "name": "ipywidgets", |
| 226 | + "pinned": True, |
| 227 | + "ver": ipyw_version, |
| 228 | + "marker": "" |
| 229 | + } |
| 230 | + pkg_order.append("ipywidgets") |
| 231 | + print(f"Added ipywidgets=={ipyw_version} (resolved from environment)") |
| 232 | + |
166 | 233 | # collect package lines |
167 | 234 | if args.sort: |
168 | 235 | pkg_keys = sorted(pkg_order, key=str.casefold) |
|
0 commit comments