扩展Python Venv:以您的方式组织依赖
#教程 #python #cicd

介绍

虚拟环境是通过隔离项目特定软件包来组织开发过程的好方法。因此,Python具有用于创建虚拟环境的内置工具venv。在本教程中,我们将通过实现将有关生产和开发相关软件包的信息存储在单独的需求文件中的信息来探讨如何扩展其功能。

本教程将指导您如何实现此类功能,并解释脚本中每个功能的解决方案和目的背后的逻辑。本教程的先决条件是Python和虚拟环境经验的基本知识。本教程中的示例需要Python 3.5+。

工具

在Python中有不同的方法来分裂依赖性。 Poetry提供了配置Python应用程序的多功能选项,包括分期:生产,开发和测试。它还为Python开发人员提供了许多其他有用的选项。

但是,尽管功能强大,但可能有理由使用更简单的替代方案,例如venv。如果您正在从事一个复杂的项目,计划将您的项目发布在Python存储库(例如PYPI)上,或者它在历史上是基于诗歌,那么与诗歌保持一致是有道理的。

其他值得一提的替代方案是Virtualenv和Pipenv。在数据科学和科学计算中,conda也具有很大的知名度。尽管这些工具提供了自定义虚拟环境的选项,但并非总是需要的。

更简单的替代方案

如果您正在从事宠物项目,简单的服务或不想使用额外的第三方工具使代码复杂化,那么使用venv可能是理想的解决方案。它是内置的Python库,具有有限的命令集,可以轻松学习。在最简单的情况下,根本不需要学习venv。只需向项目目录导航并创建虚拟环境:

python -m venv .venv

它将要做的是调用Python模块venv并在当前目录中在.venv目录中创建虚拟环境。

虚拟环境文件夹的其他通用名称是venvenv.env。现在,此文件夹包含Python的可执行文件,它将存储已安装的软件包。现在让我们安装一些软件包。但是在我们做到之前必须激活虚拟环境

source .venv/bin/activate

当您不再需要虚拟环境时,通过简单地键入deactivate将其停用。

在我们的假设场景中,我们假设我们正在烧瓶上构建一个Web应用程序,但是在此之前,让我们检查一下我们确实通过调用which python来检查我们确实处于虚拟环境中。它将产生一条通往当前使用的Python可执行文件的路径。如果路径导致当前目录并像.venv/bin/python一样结束,那么环境就会激活,我们很高兴。

安装烧瓶

pip install Flask

尤其是复杂的软件包,例如烧瓶不存在,它取决于其他将安装的Python软件包。为了确保是这种情况,请致电pip freeze。它显示了在我们的虚拟环境中具有相应版本的已安装软件包的列表。

一个好习惯是维护具有相关依赖项的文本文件requirements.txt,以便其他开发人员可以通过调用pip install -r requirements.txt安装所有必需的软件包来设置工作环境。

创建requirements.txt

pip freeze > requirements.txt

除了运行应用程序所需的软件包外,通常需要在开发和测试中使用的工具。在此示例中,我们选择一个用于测试pytest的软件包,用于根据PEP8保持代码样式,让我们安装flake8(用于手动检查)和autopep8,将正确格式化我们的代码。

pip install pytest flake8 autopep8

键入pip freeze,以查看现在有更多软件包。最后保存到文件:pip freeze > requirements.txt

保持依赖关系分开

在这里,我们到了venv简单性给我们带来一些不便的地步。当前安装的软件包可以分为2组 - 一组与烧瓶有关的软件包对于运行应用程序是必需的。但是,将测试软件包安装到生产环境中是多余的。

解决方案是维护两个单独的文件 - 一个用于应用程序包的应用程序包(requirements.txt)用于开发软件包(requirements-dev.txt)。在这种情况下,测试包也存储在-dev文件中,例如在Flasgger中。可以将依赖关系分开,将它们从原始的requirements.txt中移除并将其保存到requirements-dev.txt中。

但是,如果要安装新软件包,则应重复此过程。在这一点上,我们有两个选择:要么转向第三方虚拟环境管理工具,要么可以自动化此过程。

自动化

基本上,此过程包括两个步骤:将当前软件包列表保存到文件中:pip freeze > requirements.txt,然后一些脚本可以对将开发软件包的开发软件包转移到其开发对应物,将与应用程序相关的软件包保持在requirements.txt中。

该序列可以从终端手动执行,但是在基于UNIX的操作系统上可以执行更方便的解决方案:make实用程序。基本用法是make command-name。此实用程序称为搜索名为Makefile的文件并在命令名称部分下执行命令序列。

让我们创建我们的makefile和命令

freeze:
    pip freeze > requirements.txt
    python -m split_dependencies.py

调用make freeze将将依赖项保存到requirements.txt中,并调用python脚本以在单独的文件之间拆分依赖关系。要正确运行它,首先让我们为将来的脚本创建基础。创建文件split_dependencies.py并将以下代码放入其中:

if __name__ == "__main__":
    pass

现在运行make freeze。如果一切都完成了,正确终端将产生执行命令的列表:

pip freeze > requirements.txt
python -m split_dependencies

此时,在工作目录中显示以下文件结构:

.
├── .venv
├── Makefile
├── requirements.txt
└── split_dependencies.py

脚本架构

是时候开始开发脚本了。但是在编码之前,让我们考虑一下我们脚本应该做的事情。它将由多个函数和主要可执行函数run()组成,该功能将负责执行其他功能。

在编码功能时,最好遵循单个责任原则。因此,每个功能都只有一个任务。例如,应从文件(load_requirements)加载软件包列表,但是对于从Newline字符清洁数据,可以使用其他功能。然后,要获得清除的包装列表,这些函数将以一个序列的数据传递到另一个形成数据管道的数据。

及其接口的功能列表,该函数将在脚本中以其责任隔开:

负载

  • load_requirements(fname="requirements.txt")
  • clean_list(items: list)-从requirements.txt加载的每个字符串中删除newline字符

逻辑

  • is_dev_requirement(item: str)-检查当前软件包是否与开发类别有关
  • is_prod_requirement(item: str)-检查当前软件包是否与生产(或应用程序运行)类别有关
  • extract(criteria: Callable, data: list)-按标准(is_dev_requirementis_prod_requirement
  • 的包装列表

保存

  • prepare_data(data: list)-准备保存数据(连接到字符串的过滤软件包列表)
  • save_requirements(fname: str, data: str)-保存文件要求(requirements.txtrequirements-dev.txt

由于与开发和测试相关的软件包通常与应用程序相关软件包相比,数量的数量要少得多,因此存储有关它们的信息以进行过滤是有意义的。这是包含可能用于开发的软件包的列表的示例:

DEV_REQUIREMENTS = ["autopep8", "black", 
                    "flake8", "pytest-asyncio", 
                    "pytest", "Faker"]

在编码过程中,我们将使用Python typing来使代码更可读,以术语使用哪些数据类型,并且您的IDE可以使用此信息来提供类型的提示。

其他类型的检查器(例如mypy)可用于启用静态键入(设计是动态打字语言)。由于Python 3.5键入是内置功能。但是,可以使用typing软件包(以Python分发发货)来实现一些键入功能。

编码

使用开发的脚本体系结构让我们代码脚本。它将由run()函数管理,其中将实现脚本逻辑。

步骤1:加载requirements.txt的软件包列表并从newline字符中清理。

步骤2:形成两个包装列表 - 用于生产和开发

步骤3:准备数据并将其保存到两个文本文件中。

这是脚本的最终版本。随时尝试:

from typing import Callable

DEV_REQUIREMENTS = ["autopep8", "black", 
                    "flake8", "pytest-asyncio", 
                    "pytest", "Faker"]


def run():
    dependencies = clean_list(load_requirements())
    dev_dependencies = extract(is_dev_requirement, 
                               dependencies)
    prod_dependencies = extract(is_prod_requirement, 
                                dependencies)
    save_requirements("requirements-dev.txt", 
                      prepare_data(dev_dependencies))
    save_requirements("requirements.txt", 
                      prepare_data(prod_dependencies))


def extract(criteria: Callable, data: list) -> list:
    return list(filter(lambda item: criteria(item), data))


def is_dev_requirement(item: str) -> bool:
    package_name, _ = item.split("==")
    return package_name in DEV_REQUIREMENTS


def is_prod_requirement(item: str) -> bool:
    return not is_dev_requirement(item)


def load_requirements(fname="requirements.txt") -> list:
    with open(fname, "r") as f:
        dependencies = f.readlines()
    return dependencies


def save_requirements(fname: str, data: str) -> None:
    with open(fname, "w") as f:
        f.write(data)


def prepare_data(data: list) -> str:
    return "\n".join(data) + "\n"


def clean_list(items: list) -> list:
    return list(map(lambda item: item.strip(), items))


if __name__ == "__main__":
    run()

评论

  • load_requirements-使用readlines方法从文件加载软件包
  • clean_list从每个字符串中删除了新线字符,该字符的映射将item.strip()应用于items列表的每个元素
  • extract-使用内置功能filter过滤初始列表59
  • is_dev_requirement-获取一个带有有关软件包的信息的字符串(示例:Flask==2.2.3),提取程序包名称及其版本,并检查是否包含在开发包列表中的软件包
  • is_prod_requirement-检查软件包是否与生产有关,致电is_dev_requirement并返回否定结果
  • prepare_data加入newline字符的包装列表,并在结束时添加一个包装,因为根据Unix基于unix的opearation opearation Systems
  • save_requirements-将数据写入文件,两次称为文件,因为软件包被保存到两个单独的文件中。使用w标志进行写作,因为这是文本数据
  • run-实现脚本逻辑并确保功能之间的数据交换

现在,如果某人想从事该项目以设置依赖项,则应从两个要求文件中安装依赖项:

pip install -r requirements.txt
pip install -r requirements-dev.txt

结论

在本教程中,我们研究了使用一个简单的Python脚本扩展venv功能的方式,该脚本可以通过项目运输。关于此脚本可扩展性的选项,可以做很多事情。例如,通过引入测试阶段或应用排序以使软件包列表更加方便,将软件包分为三个部分,而不是两个部分。后者在具有很多依赖性的大型项目中特别方便。

您可以在此处找到本教程中使用的文件和代码:https://github.com/dmikhr/split-dependencies-demo