防止私有包和基于 pypi 的包之间的命名空间冲突 Python

Preventing namespace collisions between private and pypi-based Python packages

我们有 100 多个私有包,到目前为止,我们一直在使用 s3pypi 在 s3 存储桶中设置私有 pypi。我们的私有包相互依赖(并且依赖 public 个包),而且(当然)我们的 GitLab 管道找到它所依赖的包的最新功能版本是很重要的。 IE。我们对最新签入的代码不感兴趣。我们仅在测试后创建新轮子并且 qa 运行 反对推动掌握(这是解释 -e <vcs> 要求不起作用的冗长方式)。

我们的设置工作得非常好,直到有人在官方 pypi 上创建了一个新的 public 包,它隐藏了我们的包名称之一。我们可以通过增加版本号来强制选择我们的私人包,使其高于 pypi.org 上的新包 - 或者通过将我们的包重命名为尚未在 pypi.org 上使用的包。

这显然是一个 hacky 和脆弱的解决方案,但显然功能是这样的 by-design

初始存储桶设置后,s3pypi 不需要维护或管理。上面的票建议使用 devpi 但这似乎是一个非常繁重的解决方案,需要 administration/monitoring/etc.

GitLab 的 pypi 解决方案似乎是在单个包级别(这意味着我们必须列出多达 100 多个 url - 每个包一个)。这似乎不切实际,但也许我不理解某些东西(我也可以在我们的组下看到包注册表菜单,但文档指向“package-pypi”文档)。

我们不会是第一个遇到这个问题的小公司吧..?有没有比在 pypi.org 上注册我们所有包的虚拟版本更好的方法(版本=0.0.1,所以 s3pypi 版本将是首选)?

这可能不是您的解决方案,但我告诉您我们的做法。

  1. 为包名称添加前缀,并使用命名空间(例如 company.product.tool)。
  2. 当我们安装我们的包(包括它们的 in-house 依赖项)时,我们使用 requirements.txt 文件包括我们的 PyPI URL。我们 运行 容器中的所有内容,并在构建映像时在其中安装所有 public 依赖项。

您的公司可以将所有对 pypi 的请求重定向到您首先控制的服务(也许就在您的构建服务器的 hosts 文件中)

这可能会让您

  • prefer/override 带有本地包的任意包
  • 检测此类案例
  • 在本地缓存common/large 上游包
  • 拒绝 suspect/non-known versions/names 的 upstream packages

我们为此使用 VCS。我看到您已经明确排除了这种可能性,但是您是否考虑过使用分支来标记您在 VCS 中的最新稳定版本?

如果您对最新版本的 master 或 dev 分支不感兴趣,但您 运行 test/QA 反对提交,那么我会将您的 test/QA 套件配置为合并到一个名为“稳定”或“pypi-stable”的分支中,然后您的需求文件如下所示:

pip install git+https://gitlab.com/yourorg/yourpackage.git@pypi-stable

相同的配置将适用于 setup.py 个需求块(允许链式内部依赖项)。

我是不是漏掉了什么?

您也许可以通过 requirements.txt 和两个 pip 调用获得您正在寻找的行为:

cat requirements.txt | xargs -n 1 pip install -i <your-s3pipy>
pip install -r requirements.txt

第一个尝试从您的本地存储库安装它可以安装的东西,如果失败则忽略一个包。第二次调用尝试从 pipy 安装之前失败的所有内容。

这是可行的,因为 --upgrade-strategy only-if-needed 是默认值(我相信从 pip 10.X 开始,不要引用我的话)。如果您使用的是旧 pip,则可能需要手动指定。


此方法的一个限制是,如果您 expect/request 一个本地包,但它不存在并且 pipy 上存在同名包。在这种情况下,您将获得该包裹。不确定这是否是一个问题。

@a_guest 对我的第一个回答的评论让我开始思考,“问题”是 pip 在对候选者进行排序以满足要求时没有考虑包的来源。

所以这里有一个可能的方法来改变它:Monkey-patch pip 并引入对索引的偏好。

from __future__ import absolute_import
import os
import sys

import pip
from pip._internal.index.package_finder import CandidateEvaluator


class MyCandidateEvaluator(CandidateEvaluator):
    def _sort_key(self, candidate):
        (has_allowed_hash, yank_value, binary_preference, candidate.version,
         build_tag, pri) = super()._sort_key(candidate)

        priority_index = "localhost"  #use your s3pipy here
        if priority_index in candidate.link.comes_from:
            priority = 1
        else:
            priority = 0

        return (has_allowed_hash, yank_value, binary_preference, priority,
                candidate.version, build_tag, pri)


pip._internal.index.package_finder.CandidateEvaluator = MyCandidateEvaluator

# Remove '' and current working directory from the first entry
# of sys.path, if present to avoid using current directory
# in pip commands check, freeze, install, list and show,
# when invoked as python -m pip <command>
if sys.path[0] in ('', os.getcwd()):
    sys.path.pop(0)

# If we are running from a wheel, add the wheel to sys.path
# This allows the usage python pip-*.whl/pip install pip-*.whl
if __package__ == '':
    # __file__ is pip-*.whl/pip/__main__.py
    # first dirname call strips of '/__main__.py', second strips off '/pip'
    # Resulting path is the name of the wheel itself
    # Add that to sys.path so we can import pip
    path = os.path.dirname(os.path.dirname(__file__))
    sys.path.insert(0, path)

from pip._internal.cli.main import main as _main  # isort:skip # noqa


if __name__ == '__main__':
    sys.exit(_main())

设置一个requirements.txt

numpy
sampleproject

并使用与 pip.

相同的参数调用上述脚本
>python mypip.py install --no-cache --extra-index http://localhost:8000 -r requirements.txt
Looking in indexes: https://pypi.org/simple, http://localhost:8000
Collecting numpy
  Downloading numpy-1.19.1-cp37-cp37m-win_amd64.whl (12.9 MB)
     |████████████████████████████████| 12.9 MB 6.8 MB/s
Collecting sampleproject
  Downloading http://localhost:8000/sampleproject/sampleproject-0.5.0-py2.py3-none-any.whl (4.3 kB)
Collecting peppercorn
  Downloading peppercorn-0.6-py3-none-any.whl (4.8 kB)
Installing collected packages: numpy, peppercorn, sampleproject
Successfully installed numpy-1.19.1 peppercorn-0.6 sampleproject-0.5.0

将此与默认的 pip 调用进行比较

>pip install --no-cache --extra-index http://localhost:8000 -r requirements.txt
Looking in indexes: https://pypi.org/simple, http://localhost:8000
Collecting numpy
  Downloading numpy-1.19.1-cp37-cp37m-win_amd64.whl (12.9 MB)
     |████████████████████████████████| 12.9 MB 6.4 MB/s
Collecting sampleproject
  Downloading sampleproject-2.0.0-py3-none-any.whl (4.2 kB)
Collecting peppercorn
  Downloading peppercorn-0.6-py3-none-any.whl (4.8 kB)
Installing collected packages: numpy, peppercorn, sampleproject
Successfully installed numpy-1.19.1 peppercorn-0.6 sampleproject-2.0.0

注意 mypip 更喜欢可以从 localhost 检索到的包;当然,您可以进一步自定义此行为。