如何禁用将包上传到 PyPi 除非将 --public 传递给上传命令

cko*_*kot 5 python setuptools pypi devpi

我正在开发包并将包的开发/测试/等版本上传到本地 devpi 服务器。

为了防止意外上传到PyPi,我采用了以下常见做法:

setup(...,
      classifiers=[
        "Programming Language :: Python",
        "Programming Language :: Python :: 2",
        "Programming Language :: Python :: 2.7",
        "Private :: Do not Upload"
     ],
     ...)
Run Code Online (Sandbox Code Playgroud)

效果很好,但是当我最终准备好将包上传到 PyPi 时呢?

我想出了一个完全丑陋但简单的技巧,它要求我将分类器定义为 setup() 调用之外的全局变量,如下所示:

CLASSIFIERS = [
    "Programming Language :: Python",
    "Programming Language :: Python :: 2",
    "Programming Language :: Python :: 2.7"
]


if "--public" not in sys.argv:
     CLASSIFIERS.append("Private :: Do Not Upload")
else:
     sys.argv.remove("--public")

setup(...
      classifiers=CLASSIFIERS,
      ...)
Run Code Online (Sandbox Code Playgroud)

另一个也许更简单的选择是仅仅注释掉“私人::请勿上传”,但这似乎并不比我的黑客更专业。

我想做的是创建一个名为的上传命令的适当子类SafeUpload,并让它检查--publiccmd-line 选项。也许,由于在上传之前可能存在构建,因此SafeBuild可能是更好的选择。

不幸的是,我无法理解有关创建自定义命令的 setuptools 文档。

有谁知道如何实现这个?我不清楚自定义命令是否可以访问传递给 的参数setup(),即它是否可以直接操作classifiers传递给 的setup()参数,或者是否要求命令的用户遵循将 CLASSIFIERS 定义为全局变量的约定?

met*_*ter 5

倒退你的问题;虽然它确实很广泛,但主题仍然足够有限。

我可以告诉你,分类器不是被操纵的,而是从 中读取,然后PKG-INFO通过egg_info命令写入文件,该命令依次查找函数将执行实际写入的所有egg_info.writers入口点。setuptools.command.egg_info:write_pkg_info据我所知,尝试在外部利用该分类器并不是一个好方法,但是您可以覆盖您想要的所有内容和任何内容setuptools,以便您可以创建自己的write_pkg_info函数,弄清楚如何读取元数据(您可以看到主要在distutils.command.upload:upload.upload_file方法中)并在 upload_file 最终读取它之前进一步操作它。此时,您可能会认为操纵和使用这个系统会相当烦人。

正如我所提到的,一切都可以被覆盖。您可以创建一个带有公共标志的上传命令,如下所示:

from distutils.log import warn
from distutils.command.upload import upload as orig
# alternatively, for later versions of setuptools:
# from setuptools.command.upload import upload as orig

class upload(orig):
    description = "customized upload command"

    user_options = orig.user_options + [
        ('public', None,
         'make package public on pypi'),
    ]

    def initialize_options(self):
        orig.initialize_options(self)
        self.public = False

    def run(self):
        if not self.public:
            warn('not public, not uploading')
            return
        return orig.run(self)
Run Code Online (Sandbox Code Playgroud)

陪伴者setup.py可能看起来像这样。

from setuptools import setup

setup(
    name='my_pypi_uploader',
    version='0.0',
    description='"safer" pypi uploader',
    py_modules=['my_pypi_uploader'],  # assuming above file is my_py_uploader.py
    entry_points={
        'distutils.commands': [
            'upload = my_pypi_uploader:upload',
        ],
    },
)
Run Code Online (Sandbox Code Playgroud)

将其作为包安装到您的环境中,上传命令将替换为您的版本。运行示例:

$ python setup.py upload
running upload
not public, not uploading
Run Code Online (Sandbox Code Playgroud)

使用公共标志重试

$ python setup.py upload --public
running upload
error: No dist file created in earlier command
Run Code Online (Sandbox Code Playgroud)

这很好,因为我根本没有创建任何 dist 文件。您当然可以通过重写来进一步扩展该命令upload_file方法(在代码中复制)来进一步扩展该命令,并更改部分以在子类中执行您想要的操作(例如在那里注入私有分类器),由您决定。

您可能还想知道为什么类名是小写的(违反 pep8),这是由于遗留问题以及给定命令的帮助是如何生成的。

$ python setup.py upload --help
...
Options for 'upload' command:
Run Code Online (Sandbox Code Playgroud)

使用“正确”命名的类(例如SafeUpload;记住还要更新entry_point中的setup.py来指向这个新的类名)

$ python setup.py upload --help
...
Options for 'SafeUpload' command:
Run Code Online (Sandbox Code Playgroud)

当然,如果此输出是意图,则可以使用标准类命名约定。

尽管说实话,您根本不应该在生产中指定上传,而应该在构建服务器上作为后推送挂钩的一部分执行此操作,因此当项目被推送(或标记)时,构建已完成并且文件被加载到您的私人服务器上,然后只有进一步的手动干预(或者如果推送特定标签则自动干预)才能将包上传到 pypi。但是,上面的示例应该可以帮助您开始执行最初打算做的事情。

最后一件事:如果未设置该标志,您可以更改self.repository为您的私人 devpi 位置。--public您可以在调用该方法之前覆盖它orig.upload_file(通过您的自定义版本),或者在run;中执行此操作。因此,您的代码可以验证存储库 url 不是公共 PyPI 实例,而不是退出。或者,通过self.distribution.metadata(self作为upload实例)操纵分布元数据(即分类器)。当然,您可以创建一个全新的命令来使用它(通过创建一个新的Command子类,并为其添加一个新的入口点)。