Como você gosta dessa opção de gerenciamento de dependências no Python?

Recentemente, decidi que era hora de finalmente resolver o tópico de gerenciamento de dependências em meus projetos em Python e comecei a procurar uma solução que fosse completamente adequada para mim. Eu experimentei pipenv, estudei documentação de poesia, li outros artigos relacionados. Infelizmente, nunca encontrei a solução perfeita. Como resultado, inventei uma nova bicicleta, minha abordagem, que proponho discutir sob o corte.


Problema


Mas antes de prosseguir diretamente para a descrição da abordagem, gostaria de explicar por que surgiu essa necessidade e por que as soluções existentes não se adequavam a mim.


Como parte do meu trabalho, na maioria das vezes eu uso o Python para dois propósitos: é a análise de dados e o aprendizado de máquina usando os blocos de notas Jupyter, ou pequenos scripts Python que, de alguma forma, preparam os dados. Quero observar que não estou envolvido na criação de pacotes e na publicação deles no Pypi (portanto, não me concentro nesse processo neste artigo).


Muitas vezes, preciso executar scripts ou blocos de notas criados há muito tempo. Portanto, eu precisava de alguma forma corrigir as versões de dependência e executar scripts e blocos de notas em um ambiente virtual. Por outro lado, às vezes em uma nova versão de uma biblioteca, pode aparecer uma funcionalidade que melhorará os resultados de um bloco de notas ou script antigo. Por exemplo, no scikit-learn (uma biblioteca para aprendizado de máquina), eles podem adicionar uma implementação de um novo algoritmo que funciona muito bem para o meu caso.


Muitas vezes, ao desenvolver um script, também tenho que instalar algumas dependências adicionais necessárias apenas para o desenvolvimento. Por exemplo, como uso o VSCode para desenvolvimento, é necessário que o pylint seja instalado no ambiente virtual. Outras pessoas com quem trabalho podem usar outras ferramentas de desenvolvimento que não precisam dessa dependência.


Com base nessas premissas, desenvolvi os seguintes requisitos para o gerenciamento de dependências:


  1. Eu devo conseguir separar as dependências centrais (necessárias para executar o script) e as dependências necessárias apenas para o desenvolvimento.
  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.


O código fonte dessas duas funções está armazenado no meu arquivo (sempre verifique a fonte !!!). Você pode copiá-lo para um diretório ~/.bash/e, para disponibilizar essas funções em casa, adicione as seguintes linhas em .bashrc:


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

Conclusão


Comecei a usar esta solução e talvez ainda não tenha descoberto nenhuma falha. Em geral, este post foi concebido para discutir essa abordagem e se ele tem direito à vida. Se você encontrar algum problema, ficarei muito feliz em ouvir sobre eles.


PS

, Python , ( ) .


All Articles