您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

在不修改sys.path或第三方软件包的情况下,在Python软件包中导入供应商依赖性

在不修改sys.path或第三方软件包的情况下,在Python软件包中导入供应商依赖性

首先,我建议不要出售。一些主要软件包以前曾使用过供应商,但是为了避免不得不处理供应商的痛苦,已经放弃了。requests图书馆就是一个这样的例子。如果您依靠pip install用来安装软件包的人员,则 只需使用依赖项 并向人们介绍虚拟环境。不要假设您需要承担使依赖关系不复杂的负担,也不必阻止人们在全局Pythonsite-packages位置中安装依赖项。

同时,我知道第三方工具的插件环境有所不同,并且如果对该工具使用的Python安装添加依赖项很麻烦或无法进行商贩销售,则是可行的选择。我看到Anki在.zip不支持setuptools的情况下将扩展名作为文件分发,因此肯定是这种环境。

因此,如果您选择供应商依赖性,则可以使用脚本来管理依赖性并更新其导入。这是您的选择#1,但 自动

这是pip项目选择的路径,有关其自动化的信息,请参见其tasks子目录,该子目录建立在invoke库上。请参阅pip项目供应商README,以了解其政策和原理(其中之一是pip需要自举 ,例如,可以使用其依赖项来安装任何东西)。

您不应使用任何其他选项;您已经列举了#2和#3的问题。

使用自定义导入程序的选项#4的问题在于, 您仍然需要重写import 。换句话说,所使用的自定义导入器钩子setuptools根本无法解决供应商名称间的问题,而是可以在缺少供应商软件包的情况下动态导入顶级软件包(pip通过手动 分包过程解决的问题)。setuptools实际上使用选项#1,在那里他们重写供应商软件包的源代码。例如,在packaging项目setuptools供应商子包中查看这些行;该setuptools.extern命名空间是由自定义导入钩,然后重定向要么处理setuptools._vendor 或顶级名称(如果从供应商化的软件包导入失败)。

pip自动化更新vendored包采取以下步骤:

因此,从本质上讲,该方法最重要的部分是pip重写供应商的程序包导入非常简单;为了简化逻辑并删除pip特定部分,它的解释过程很简单:

import shutil
import subprocess
import re

from functools import partial
from itertools import chain
from pathlib import Path

WHITELIST = {'README.txt', '__init__.py', 'vendor.txt'}

def delete_all(*paths, whitelist=frozenset()):
    for item in paths:
        if item.is_dir():
            shutil.rmtree(item, ignore_errors=True)
        elif item.is_file() and item.name not in whitelist:
            item.unlink()

def iter_subtree(path):
    """Recursively yield all files in a subtree, depth-first"""
    if not path.is_dir():
        if path.is_file():
            yield path
        return
    for item in path.iterdir():
        if item.is_dir():
            yield from iter_subtree(item)
        elif item.is_file():
            yield item

def patch_vendor_imports(file, replacements):
    text = file.read_text('utf8')
    for replacement in replacements:
        text = replacement(text)
    file.write_text(text, 'utf8')

def find_vendored_libs(vendor_dir, whitelist):
    vendored_libs = []
    paths = []
    for item in vendor_dir.iterdir():
        if item.is_dir():
            vendored_libs.append(item.name)
        elif item.is_file() and item.name not in whitelist:
            vendored_libs.append(item.stem)  # without extension
        else:  # not a dir or a file not in the whilelist
            continue
        paths.append(item)
    return vendored_libs, paths

def vendor(vendor_dir):
    # target package is <parent>.<vendor_dir>; foo/_vendor -> foo._vendor
    pkgname = f'{vendor_dir.parent.name}.{vendor_dir.name}'

    # remove everything
    delete_all(*vendor_dir.iterdir(), whitelist=WHITELIST)

    # install with pip
    subprocess.run([
        'pip', 'install', '-t', str(vendor_dir),
        '-r', str(vendor_dir / 'vendor.txt'),
        '--no-compile', '--no-deps'
    ])

    # delete stuff that's not needed
    delete_all(
        *vendor_dir.glob('*.dist-info'),
        *vendor_dir.glob('*.egg-info'),
        vendor_dir / 'bin')

    vendored_libs, paths = find_vendored_libs(vendor_dir, WHITELIST)

    replacements = []
    for lib in vendored_libs:
        replacements += (
            partial(  # import bar -> import foo._vendor.bar
                re.compile(r'(^\s*)import {}\n'.format(lib), flags=re.M).sub,
                r'\1from {} import {}\n'.format(pkgname, lib)
            ),
            partial(  # from bar -> from foo._vendor.bar
                re.compile(r'(^\s*)from {}(\.|\s+)'.format(lib), flags=re.M).sub,
                r'\1from {}.{}\2'.format(pkgname, lib)
            ),
        )

    for file in chain.from_iterable(map(iter_subtree, paths)):
        patch_vendor_imports(file, replacements)

if __name__ == '__main__':
    # this assumes this is a script in foo next to foo/_vendor
    here = Path('__file__').resolve().parent
    vendor_dir = here / 'foo' / '_vendor'
    assert (vendor_dir / 'vendor.txt').exists(), '_vendor/vendor.txt file not found'
    assert (vendor_dir / '__init__.py').exists(), '_vendor/__init__.py file not found'
    vendor(vendor_dir)
python 2022/1/1 18:28:21 有487人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶