防止私有包和基于 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 版本将是首选)?
这可能不是您的解决方案,但我告诉您我们的做法。
- 为包名称添加前缀,并使用命名空间(例如
company.product.tool
)。
- 当我们安装我们的包(包括它们的 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
检索到的包;当然,您可以进一步自定义此行为。
我们有 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 版本将是首选)?
这可能不是您的解决方案,但我告诉您我们的做法。
- 为包名称添加前缀,并使用命名空间(例如
company.product.tool
)。 - 当我们安装我们的包(包括它们的 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
检索到的包;当然,您可以进一步自定义此行为。