#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ХИРУРГИЧЕСКОЕ ВОССТАНОВЛЕНИЕ core.py. Что случилось: `find / -name 'core.*' -delete` (мой скрипт очистки диска) совпал не только с аварийными дампами `core.1234`, но и с `core.py`. Из 20 пакетов Python пропали файлы модулей. Почему НЕ `pip install --force-reinstall`: это меняет версии и перекладывает пакет целиком на живой торговой машине, где работают 178 сервисов. Слишком грубо для задачи «вернуть один удалённый файл». Что делаем вместо: качаем колесо ТОЙ ЖЕ версии, что установлена, достаём из него ровно недостающий файл и кладём на место. Версии не меняются, ничего не переустанавливается, остальные файлы не трогаются. python3 core_restore.py сухой прогон python3 core_restore.py --apply восстановить """ import importlib.metadata as md import os, shutil, subprocess, sys, tempfile, zipfile APPLY = "--apply" in sys.argv # пакет → внутренние пути, где мог пропасть core.py TARGETS = { "numpy": ["numpy/ma/core.py", "numpy/core/__init__.py"], "xgboost": ["xgboost/core.py"], "click": ["click/core.py"], "toolz": ["toolz/sandbox/core.py"], "pandas": ["pandas/core/__init__.py"], "scikit-learn": ["sklearn/utils/_core.py"], } IMPORT_NAME = {"scikit-learn": "sklearn"} def sh(c, t=600): r = subprocess.run(c, shell=True, capture_output=True, text=True, timeout=t) return (r.stdout or "") + (r.stderr or "") def site_dir(pkg): """Куда установлен пакет.""" try: d = md.distribution(pkg) return str(d.locate_file("")) except Exception: return None def broken(pkg): """Список путей, которых не хватает.""" base = site_dir(pkg) if not base: return None, [] miss = [rel for rel in TARGETS[pkg] if not os.path.exists(os.path.join(base, rel))] return base, miss print("=== ЧТО ПРОПАЛО ===") plan = {} for pkg in TARGETS: base, miss = broken(pkg) if base is None: print(f" {pkg:14} не установлен — пропуск") continue try: ver = md.version(pkg) except Exception: ver = "?" if miss: print(f" {pkg:14} {ver:12} НЕТ: {', '.join(miss)}") plan[pkg] = (base, ver, miss) else: print(f" {pkg:14} {ver:12} всё на месте") if not plan: print("\nвосстанавливать нечего") sys.exit() if not APPLY: print("\nсухой прогон. Для восстановления добавьте --apply") sys.exit() tmp = tempfile.mkdtemp(prefix="whl_") print(f"\n=== ВОССТАНОВЛЕНИЕ (временный каталог {tmp}) ===") for pkg, (base, ver, miss) in plan.items(): print(f"\n{pkg} {ver}") out = sh(f"pip3 download '{pkg}=={ver}' --no-deps --only-binary=:all: -d {tmp} 2>&1 | tail -2") whls = [f for f in os.listdir(tmp) if f.lower().startswith(pkg.replace("-", "_").lower()) or f.lower().startswith(pkg.lower())] if not whls: print(f" ⛔ колесо не скачалось: {out.strip()[:120]}") continue whl = os.path.join(tmp, sorted(whls)[-1]) print(f" колесо: {os.path.basename(whl)}") with zipfile.ZipFile(whl) as z: names = set(z.namelist()) for rel in miss: if rel not in names: print(f" ⚠️ {rel} — в колесе нет такого пути, пропуск") continue dst = os.path.join(base, rel) os.makedirs(os.path.dirname(dst), exist_ok=True) with z.open(rel) as src, open(dst, "wb") as f: shutil.copyfileobj(src, f) os.chmod(dst, 0o644) print(f" ✅ восстановлен {rel}") pc = os.path.join(base, os.path.dirname(miss[0]), "__pycache__") if os.path.isdir(pc): shutil.rmtree(pc, ignore_errors=True) print("\n=== ПРОВЕРКА ИМПОРТОМ ===") for pkg in TARGETS: name = IMPORT_NAME.get(pkg, pkg) r = sh(f"python3 -c 'import {name}' 2>&1 | tail -1").strip() print(f" {'❌' if r else '✅'} {name:14} {r[:60]}") for extra in ("numpy.ma", "toolz.sandbox"): r = sh(f"python3 -c 'import {extra}' 2>&1 | tail -1").strip() print(f" {'❌' if r else '✅'} {extra:14} {r[:60]}") shutil.rmtree(tmp, ignore_errors=True)