Automatyczna instalacja brakujących bibliotek Pythona

https://xpil.eu/bGaDM

Po krótkiej przygodzie z Linuksem podjąłem jeszcze jedną próbę instalacji Windows, który z jakiegoś tajemniczego powodu szedł mi ostatnio w krzaki od razu po instalacji. Winnym okazał się dysk NVMe, który miałem wpięty za pośrednictwem jakiegoś taniego chińskiego kontrolera PCI Express.

Problem, okazało się, był taki, że mój staruszek BIOS nie rozpoznawał tego napędu w ogóle, natomiast jakimś cudem Windows po ostatniej aktualizacji zaczął traktować go jako dysk C:, czyli bez sensu.

Usunięcie kontrolera pozbawiło mnie wprawdzie 2TB przestrzeni dyskowej, ale sprawiło, że Windows znów działa. Tym razem, wbrew temu, co sobie już wiele razy wcześniej obiecywałem, zainstalowałem znów Windows 11 i póki co jakoś żyję.

I tu przechodzimy do meritum dzisiejszego wpisu, czyli instalacji brakujących bibliotek Pythona. Otóż w ramach swoich rozlicznych eksperymentów z pisaniem kodu dorobiłem się już 78 mini-projektów, każdy w swoim folderze na Dropbox. Większość z nich w Pythonie.

Oczywiście każdy porządny programista trzymałby te projekty w osobnych wirtualnych środowiskach Pythona, gwarantując tym samym brak kłopotów z kompatybilnością między projektami. Ja na szczęście obok porządnego programisty nawet nie stałem, więc zamiast tego po prostu instaluję sobie wszystkie brakujące biblioteki jak leci i już.

Z tym, że jest tu pogrzebany pewien haczyk (hak czy haczyk, jeden pies), mianowicie bibliotek tych jest całkiem sporo i instalowanie każdej z nich z osobna jest dość upierdliwe. Dlatego napisałem sobie dwa niewielkie skrypty w Pythonie, które mi ten proces niejako automatyzują (albo przynajmniej pół-automatyzują).

Pierwszy skrypt wypluwa nazwy wszystkich bibliotek we wszystkich projektach do osobnego pliku tekstowego:

import os
import re

def find_py_files(directory):
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith('.py'):
                yield os.path.join(root, file)

def extract_libraries(file_path):
    libraries = set()
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            if line.startswith('import ') or line.startswith('from '):
                library = line.split()[1].split('.')[0]
                libraries.add(library)
    return libraries

def main():
    directory = 'D:\\Dropbox\\code'
    all_libraries = set()

    for py_file in find_py_files(directory):
        libraries = extract_libraries(py_file)
        all_libraries.update(libraries)

    with open('D:\\combined_libraries.txt', 'w') as file:
        for library in sorted(all_libraries):
            file.write(library + '\n')

    print(f"Combined libraries list created at D:\\combined_libraries.txt")

if __name__ == "__main__":
    main()

"Na twardo" zakodowane są tutaj dwie ścieżki: jedna do folderu z projektami (D:\Dropbox\code) i druga do pliku wynikowego D:\combined_libraries.txt. Jeżeli chcesz ukraść powyższy kod dla siebie, pamiętaj, żeby poustawiać te ścieżki po swojemu.

Drugi skrypt natomiast czyta zawartość pliku generowanego przez pierwszy skrypt i instaluje brakujące biblioteki. U mnie ten drugi skrypt wymaga uruchomienia z terminala Administratora:

import subprocess
import os
from stdlib_list import stdlib_list


def get_installed_libraries():
    result = subprocess.run(['pip', 'list'], stdout=subprocess.PIPE)
    installed_libraries = result.stdout.decode('utf-8').split('\n')
    return {line.split()[0].lower() for line in installed_libraries if line}


def get_required_libraries(file_path):
    with open(file_path, 'r') as file:
        return {line.strip().lower() for line in file if line.strip()}


def find_missing_libraries(installed, required):
    return required - installed


def install_library(library):
    subprocess.run(['pip', 'install', library], stdout=subprocess.PIPE)

def get_standard_libraries():
    return set(stdlib_list())

def main():
    standard_libraries = get_standard_libraries()
    installed_libraries = get_installed_libraries()
    required_libraries = get_required_libraries('D:\\combined_libraries.txt')

    non_standard_required = required_libraries - standard_libraries

    missing_libraries = find_missing_libraries(installed_libraries, non_standard_required)


    if missing_libraries:
        print("Installing missing libraries...")
        for library in missing_libraries:
            print(f"Installing {library}...")
            install_library(library)
        print("Installation complete.")
    else:
        print("No missing libraries. All required libraries are installed.")


if __name__ == "__main__":
    main()

Założenia są tutaj takie, że mamy już zainstalowanego Pythona, działający pip, a także - o, ironio - że mamy zainstalowaną bibliotekę stdlib_list, którą trzeba doinstalować ręcznie przed uruchomieniem tego skryptu 🙂 Dlatego też wspomniałem wcześniej, że metoda jest półautomatyczna.

Dla wszystkich moich 78 mini-projektów uruchomienie pierwszego skryptu trwa około 5 sekund, natomiast drugi wykonuje się ciut ponad 6 minut. Mógłbym co prawda zoptymalizować go do pojedynczego zawołania komendy pip, co mogłoby nieco skrócić czas wykonania, ale mi się nie chce. Niewarta skórka wyprawki.

https://xpil.eu/bGaDM

4 komentarze

  1. Oj, dziwne.
    1. Nie zawsze nazwa używana przy import jest taka sama jak nazwa biblioteki do instalacji.
    2. pip umie czytać z pliku (-r requirements.txt) i zapisywać do niego (freeze).
    A w ogóle to venv i osobne zależności per projekt polecam.

  2. To wygląda jak problem z trybu bifurkacji PCIe (podziałem x16, na 4x x4 (trym 4x4x4x4). Bo większość tanich adapterów PCIe > NVMe nie posiada wewnętrznego split i opiera się na możliwościach BIOSa

    1. Zgadza się. Producent nawet zaznacza, że maszyny klasy stacji roboczych, które nie obsługują bifurkacji, nie będą działać z tą przejściówką jeżeli spróbujemy podpiąć więcej dysków. Kłopot u mnie polega jednak na tym, że chociaż mam tam tylko jeden dysk (jak na zdjęciu), BIOS go nie widzi; większość OS-ów jakoś go jednak rozpoznaje przy starcie, a w przypadku Windows – ustawia go jako dysk główny systemowy czyli C:, nawet jeżeli podczas instalacji wybraliśmy partycję systemową z innego fizycznego napędu.

Leave a Comment

Komentarze mile widziane.

Jeżeli chcesz do komentarza wstawić kod, użyj składni:
[code]
tutaj wstaw swój kod
[/code]

Jeżeli zrobisz literówkę lub zmienisz zdanie, możesz edytować komentarz po jego zatwierdzeniu.