Wie gefällt Ihnen diese Option zur Abhängigkeitsverwaltung in Python?

Ich habe kürzlich beschlossen, dass es Zeit ist, das Thema Abhängigkeitsmanagement in meinen Python-Projekten endlich zu klären, und habe nach einer Lösung gesucht, die genau zu mir passt. Ich experimentierte mit pipenv, studierte die Dokumentation für Gedichte und las andere verwandte Artikel. Leider habe ich nie die perfekte Lösung gefunden. Als Ergebnis habe ich ein neues Fahrrad erfunden , meinen Ansatz, den ich unter dem Schnitt diskutieren möchte.


Problem


Bevor ich jedoch direkt zur Beschreibung des Ansatzes übergehe, möchte ich erläutern, warum ein solcher Bedarf entstanden ist und warum die vorhandenen Lösungen nicht zu mir passten.


Als Teil meiner Arbeit verwende ich Python meistens für zwei Zwecke: Entweder Datenanalyse und maschinelles Lernen mit Jupyter-Notizbüchern oder kleine Python-Skripte, die die Daten irgendwie vorbereiten. Ich möchte darauf hinweisen, dass ich nicht an der Erstellung und Veröffentlichung von Paketen in Pypi beteiligt bin (daher konzentriere ich mich in diesem Artikel nicht auf diesen Prozess).


Sehr oft muss ich Skripte oder Notizblöcke ausführen, die vor langer Zeit erstellt wurden. Daher musste ich die Abhängigkeitsversionen irgendwie reparieren und Skripte und Notizblöcke in einer virtuellen Umgebung ausführen. Andererseits kann manchmal in einer neuen Version einer Bibliothek eine Funktionalität auftreten, die die Ergebnisse eines alten Notizblocks oder Skripts verbessert. In scikit-learn (einer Bibliothek für maschinelles Lernen) können sie beispielsweise eine Implementierung eines neuen Algorithmus hinzufügen, der für meinen Fall hervorragend geeignet ist.


Sehr oft muss ich beim Entwickeln eines Skripts auch einige zusätzliche Abhängigkeiten installieren, die nur für die Entwicklung erforderlich sind. Da ich beispielsweise VSCode für die Entwicklung verwende, muss pylint in der virtuellen Umgebung installiert sein. Andere Personen, mit denen ich zusammenarbeite, können andere Entwicklungstools verwenden, die diese Abhängigkeit überhaupt nicht benötigen.


Basierend auf diesen Annahmen habe ich die folgenden Anforderungen für das Abhängigkeitsmanagement entwickelt:


  1. Ich sollte in der Lage sein, die zentralen Abhängigkeiten (die zum Ausführen des Skripts benötigt werden) und die Abhängigkeiten zu trennen, die nur für die Entwicklung benötigt werden.
  2. . , . , , .
  3. .

, ( pip freeze > requirements.txt) requirements.txt, . -, . -, requirements.txt , .


, pipenv (, , , ). , , . , , . , , . Poetry .



, , (k)Ubuntu 18.04. , . , , .


, . -, . : requirements.txt , requirements-dev.txt . , bash pip-install.


pip-install
function pip-install() {
    packages=()
    dev_dependency=0
    requirements_file=
    while [ $# -gt 0 ]
    do
        case "$1" in
            -h|--help)
                echo "Usage: pip-install [-d|--dev] [-r|--req <file>] <package1> <package2> ..." 1>&2
                echo ""
                echo "This function installs provided Python packages using pip"
                echo "and adds this dependency to the file listing requirements."
                echo "The name of the package is added to the file without"
                echo "concreate version only if it is absent there." 
                echo ""
                echo "-h|--help        - prints this message and exits."
                echo "-d|--dev         - if the dependency is development."
                echo "-r|--req <file>  - in which file write the dependency."
                echo "    If the filename is not provided by default the function"
                echo "    writes this information to requirements.txt or to"
                echo "    requirements-dev.txt if -d parameter is provided."
                echo "<package1> <package2> ..."
                return 0
                ;;
            -d|--dev)
                shift
                dev_dependency=1
                ;;
            -r|--req)
                shift
                requirements_file="$1"
                echo "Requirements file specified: $requirements_file"
                shift
                ;;
            *)
                packages+=( "$1" )
                echo "$1"
                shift
                ;;
        esac
    done

    if ! [ -x "$(command -v pip)" ]; then
        echo "Cannot find pip tool. Aborting!"
        exit 1
    fi

    echo "Requirements file: $requirements_file"
    echo "Development dependencies: $dev_dependency"
    echo "Packages: ${packages[@]}"

    if [ -z "$requirements_file" ]; then
        if [ $dev_dependency -eq 0 ]; then
            requirements_file="requirements.txt"
        else
            requirements_file="requirements-dev.txt"
        fi
    fi

    for p in "${packages[@]}"
    do
        echo "Installing package: $p"
        pip install $p
        if [ $? -eq 0 ]; then
            echo "Package installed successfully"
            echo "$p" >> $requirements_file
            if [ $(grep -Ec "^$p([~=!<>]|$)" "$requirements_file") -eq 0 ]; then
                echo "$p" >> $requirements_file
            else
                echo "Package $p is already in $requirements_file"
            fi
        else
            echo "Cannot install package: $p"
        fi
    done
}

, : pip-install scikit-learn pip-install --dev pylint. , scikit-learn pip ( ) requirements.txt. , requirements-dev.txt. , , , ( ). , .


, pip-freeze. pip-freeze requirements.txt, requirements.lock . , pip install -r requirements.lock. pip-freeze --dev , requirements-dev.txt.


pip-freeze
function pip-freeze() {
    dump_all=0
    dev_dependency=0
    requirements_file=
    while [ $# -gt 0 ]
    do
        case "$1" in
            -h|--help)
                echo "Usage: pip-freeze [-a|--all] [-d|--dev] [-r|--req <file>]" 1>&2
                echo ""
                echo "This function freezes only the top-level dependencies listed"
                echo "in the <file> and writes the results to the <file>.lock file."
                echo "Later, the data from this file can be used to install all"
                echo "top-level dependencies." 
                echo ""
                echo "-h|--help        - prints this message and exits."
                echo "-d|--dev         - if the dependency is development."
                echo "-a|--all         - if we should freeze all dependencies"
                echo "  (not only top-level)."
                echo "-r|--req <file>  - what file to use to look for the list of"
                echo "    top-level dependencies. The results will be written to"
                echo "    the \"<file>.lock\" file." 
                echo "    If the <file> is not provided by default the function"
                echo "    uses \"requirements.txt\" or \"requirements-dev.txt\""
                echo "    if -d parameter is provided and writes the results to the"
                echo "    \"requirements.txt.lock\" or \"requirements-dev.txt.lock\""
                echo "    correspondingly."
                return 0
                ;;
            -d|--dev)
                shift
                echo "Development dependency"
                dev_dependency=1
                ;;
            -a|--all)
                shift
                dump_all=1 
                ;;
            -r|--req)
                shift
                requirements_file="$1"
                echo "Requirements file specified: $requirements_file"
                shift
                ;;
        esac
    done

    if ! [ -x "$(command -v pip)" ]; then
        echo "Cannot find pip tool. Aborting!"
        exit 1
    fi

    if [ -z "$requirements_file" ]; then
        if [ $dev_dependency -eq 0 ]; then
            requirements_file="requirements.txt"
        else
            requirements_file="requirements-dev.txt"
        fi
    fi

    lock_file="$requirements_file.lock"
    if [ $dump_all -eq 1 ] 
    then
        pip freeze > "$lock_file"
        if [ $? -eq 0 ]; then
            echo "Locked all dependencies to: $lock_file"
        else
            echo "Error happened while locking all dependencies"
        fi
    else
        cmd_output=$(pip freeze -r "$requirements_file")
        if [ $? -eq 0 ]; then
            > "$lock_file"
            while IFS= read -r line; do
                if [ "$line" = "## The following requirements were added by pip freeze:" ]; then
                    break
                fi
                echo "$line" >> "$lock_file"
            done <<< "$cmd_output"
        fi
    fi
}

, 4 : requirements.txt, requirements-dev.txt, requirements.lock requirements-dev.lock.


Der Quellcode dieser beiden Funktionen ist in meiner Datei gespeichert (immer die Quelle überprüfen !!!). Sie können es in ein Verzeichnis kopieren ~/.bash/und die folgenden Zeilen hinzufügen, um diese Funktionen zu Hause verfügbar zu machen .bashrc:


if [ -f ~/.bash/pip_functions.sh ]; then
    source ~/.bash/pip_functions.sh
fi

Fazit


Ich habe gerade angefangen, diese Lösung zu verwenden, und vielleicht habe ich noch keine Fehler entdeckt. Im Allgemeinen wurde dieser Beitrag konzipiert, um diesen Ansatz zu erörtern und um zu erörtern, ob er das Recht auf Leben hat. Wenn Sie Probleme sehen, würde ich mich sehr freuen, davon zu hören.


PS

, Python , ( ) .


All Articles