كيف تحب خيار إدارة التبعية هذا في Python؟

قررت مؤخرًا أن الوقت قد حان لفرز موضوع إدارة التبعية في مشروعات Python الخاصة بي وبدأت في البحث عن حل يناسبني تمامًا. لقد جربت pipenv ، درست وثائق الشعر ، وقرأت مقالات أخرى ذات صلة. لسوء الحظ ، لم أجد أبداً الحل الأمثل. ونتيجة لذلك ، اخترعت دراجة جديدة ، نهجي ، والتي أقترح مناقشتها تحت القطع.


مشكلة


ولكن قبل الشروع مباشرة في وصف النهج ، أود أن أشرح لماذا نشأت هذه الحاجة ولماذا لم تكن الحلول الحالية مناسبة لي.


كجزء من عملي ، غالبًا ما أستخدم Python لغرضين: إما تحليل البيانات والتعلم الآلي باستخدام مفكرة Jupyter ، أو نصوص Python الصغيرة التي تعد البيانات بطريقة أو بأخرى. أريد أن أشير إلى أنني لا أشارك في إنشاء الحزم ونشرها في Pypi (وبالتالي ، لا أركز على هذه العملية في هذه المقالة).


في كثير من الأحيان أحتاج إلى تشغيل برامج نصية أو دفاتر ملاحظات تم إنشاؤها منذ فترة طويلة. لذلك ، كنت بحاجة إلى إصلاح إصدارات التبعية بطريقة ما وتشغيل البرامج النصية ودفاتر الملاحظات في بيئة افتراضية. من ناحية أخرى ، في بعض الأحيان في إصدار جديد من المكتبة ، قد تظهر وظائف من شأنها تحسين نتائج المفكرة أو البرنامج النصي القديم. على سبيل المثال ، في scikit-learn (مكتبة للتعلم الآلي) ، يمكنهم إضافة تنفيذ خوارزمية جديدة تعمل بشكل جيد لحالتي.


في كثير من الأحيان ، عند تطوير برنامج نصي ، يتعين علي أيضًا تثبيت بعض التبعيات الإضافية المطلوبة للتطوير فقط. على سبيل المثال ، نظرًا لأنني أستخدم VSCode للتطوير ، فإنه يتطلب تثبيت pylint في البيئة الافتراضية. يمكن للأشخاص الآخرين الذين أعمل معهم استخدام أدوات تطوير أخرى لا تحتاج إلى هذا التبعية على الإطلاق.


بناءً على هذه الافتراضات ، قمت بتطوير المتطلبات التالية لإدارة التبعية:


  1. يجب أن أكون قادراً على فصل التبعيات المركزية (اللازمة لتشغيل البرنامج النصي) والتبعيات اللازمة للتطوير فقط.
  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.


يتم تخزين التعليمات البرمجية المصدر من هاتين الوظيفتين في بلدي ملف (دائما التحقق من مصدر !!!). يمكنك نسخه إلى دليل ~/.bash/، وإتاحة هذه الوظائف في المنزل ، أضف السطور التالية إلى نفسك في .bashrc:


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

استنتاج


لقد بدأت للتو في استخدام هذا الحل وربما لم أكتشف بعد أي عيوب. بشكل عام ، تم تصميم هذا المنشور من أجل مناقشة هذا النهج وما إذا كان لديه الحق في الحياة. إذا رأيت أي مشاكل ، سأكون سعيدًا جدًا لسماعها.


ملاحظة

, Python , ( ) .


All Articles