创建包含独立可安装子包的name_space包



我目前正在尝试将多个相关包分组到一个父包(meta_package(中。在这样做的同时,我还希望其中一个软件包可以作为独立软件包安装。为此,我创建了以下文件夹结构:

├── meta_package
│   ├── subpackage1
│   │   ├── module.py
│   │   ├── __init__.py
│   ├── subpackage2
│   │   ├── module.py
│   │   └── __init__.py
│   ├── subpackage3
│   │   ├── module.py
│   │   └── __init__.py
│   └── installable_subpackage
│       ├── README.md
│       ├── __init__.py
│       ├── requirements.txt
│       ├── setup.py
│       ├── installable_subpackage
│       │   ├── __init__.py
│       │   └── submodule
│       │       ├── __init__.py
│       │       └── module.py

尽管上面的结构实现了所需的结果,但无论是在将子包定义为命名空间包还是普通包时,它都引入了额外的installable_subpackage目录。因此,要从installable_subpackage导入类,我必须使用以下导入语句:

from meta_package.installable_subpackage.installable_subpackage.submodule.module import Class

I、 但是,希望能够使用以下(较短的(导入语句导入类:

from meta_package.installable_subpackage.submodule.module import Class

我已经试过了

使用名称空间包(_S(

我尝试将命名空间包用于子包,而不是使用普通包。然而,这并没有解决额外的文件夹问题,还引入了许多python导入陷阱。

导入installable_subpackage中的冗余文件夹(模块(init.py文件

我还尝试在installable_subpackage.__init__.py文件中导入installable_subpackage子模块:

import meta_package.installable_subpackage.installable_subpackage

然而,这似乎不起作用,因为meta_package.installable_subpackage.submodule导入路径没有指向meta_package.installable_subpackage.installable_subpackage.submodule模块。我认为这是因为这个方法只适用于类而不适用于模块。

使用setuptools软件包和package_dir参数

最后,根据这个问题,我尝试在meta_packagesetup.py中使用setuptoolspackagespackage_dir参数来消除多余的文件夹。为此,我使用了以下setup.py:

from setuptools import setup, find_namespace_packages
setup(
name="meta_package",
...
packages=find_namespace_packages(include=["meta_package.*"]),
package_dir={"meta_package.installable_subpackage": "meta_package/installable_subpackage/installable_subpackage"},
)

然而,这似乎也没有消除额外的文件夹。

问题

我试图实现的包装结构可能吗?此外,如果是,是鼓励还是建议不要使用它?

系统信息

  • Python版本:Python 3.8.5
  • 虚拟环境:Conda

@sinoroc:指出

使用package_dir时,您可以"如果没有真正使用find_namespace_packages,则必须手动编写列表或在将其分配给包之前对其进行修改。

因此,为了实现所需的行为,我必须在将包列表提供给setuptools.setup方法之前修改它。这可以通过几种方式来实现。

1.将虚拟缩短包添加到包列表

我们可以将每个冗余文件夹的额外(缩短(模块条目添加到packages列表中。这可以通过使用以下setup.py文件来完成:

setup.py文件

from setuptools import setup, find_namespace_packages
# Retrieve package list
PACKAGES = find_namespace_packages(include=["meta_package*"])+["meta_package.installable_subpackage"]
setup(
name="meta_package",
...
packages=PACKAGES,
package_dir={
"meta_package.installable_subpackage": "meta_package/installable_subpackage/installable_subpackage",
},
)

要对许多子包自动执行此操作,您可以使用以下代码:

# Retrieve package list
PACKAGES = find_namespace_packages(include=["meta_package*"])
# Add extra virtual shortened package for each of namespace_pkgs that contain redundant folders
namespace_pkgs = ["installable_subpackage"]
exclusions = r"|".join(
[r"." + item + r".(?=" + item + r".)" for item in namespace_pkgs]
)
PACKAGE_DIR = {}
for package in PACKAGES:
sub_tmp = re.sub(exclusions, ".", package)
if sub_tmp is not package:
PACKAGE_DIR[sub_tmp] = package.replace(".", "/")
PACKAGES.extend(PACKAGE_DIR.keys())
setup(
name="meta_package",
...
packages=PACKAGES,
package_dir=PACKAGE_DIR,
)

2.修改包列表,使其仅包含缩短的包

或者,如果您想用更短的名称完全替换长模块名称,可以使用以下setup.py:

setup.py文件

from setuptools import setup, find_namespace_packages
# Retrieve package list
PACKAGES = find_namespace_packages(include=["meta_package*"])
# Remove redundant folders from the package list
PACKAGES = [re.sub(r".installable_subpackage.(?=installable_subpackage.)", ".", package) for package in PACKAGES]
setup(
name="meta_package",
...
packages=PACKAGES,
package_dir={
"meta_package.installable_subpackage": "meta_package/installable_subpackage/installable_subpackage",
},
)

这也可以使用以下代码对许多子包自动完成:

# Remove redundant folder from package list
red_folders = ["installable_subpackage"]
exclusions = r"|".join(
[r"." + item + r".(?=" + item + r".)" for item in red_folders]
)
PACKAGE_DIR = {}
for index, package in enumerate(PACKAGES):
sub_tmp = re.sub(exclusions, ".", package)
if sub_tmp is not package:
PACKAGES[index] = sub_tmp

重要讲话

  • 在使用上述方法时,需要注意的是,如果包是在开发模式下安装的,则这些方法还不起作用(请参阅本期(。因此,最好使用第一种方法,因为开发人员仍然可以使用较长的模块名称,而用户也可以使用较短的模块名称
  • 请记住,如果命名空间包根文件夹中有__init__.py文件,则上述方法不起作用(请参阅setuputils文档(

更新

在我得到这个答案后,我试图将这个逻辑放入setup.cfg文件中,以便它与PEP517/518兼容。在做这件事的时候,我遇到了一些问题。这个问题的解决方案可以在setuptools GitHub页面上找到。这里可以找到一个示例存储库。

相关内容

最新更新