背景
在 Python 项目开发中,随着代码包数量和复杂度的增加,为了更好地管理多个代码包的命名空间及其依赖,推荐使用 PEP 420 提供的命名空间包功能。通过这种方式,可以构建属于同一发行商(vendor)下的多个独立代码包,且这些包可以分别位于不同的代码仓库中。
在此基础上,某些代码包可能需要进一步支持可选功能模块(例如 optional1
和 optional2
),用户可以根据需要选择安装这些功能模块。本文将分阶段说明如何实现:
- 按 PEP 420 构建统一的目录风格以管理同一 vendor 下的多个代码包。
- 为某个代码包添加支持可选功能的扩展设计及测试策略。
项目目录结构
按 PEP 420 构建命名空间包
以下是适用于多个代码包的推荐目录结构,每个代码包独立版本管理,且可以分别推送到 PyPI:
vendor-package1-repo/
├── src/
│ └── vendor/
│ └── package1/
│ ├── __init__.py # 定义主包的接口
│ ├── core.py # 主包核心功能
├── tests/
│ └── test_core.py # 测试主包功能
├── setup.py # 配置分发信息
├── README.md # 项目说明文档
└── pyproject.toml # 构建工具配置
vendor-package2-repo/
├── src/
│ └── vendor/
│ └── package2/
│ ├── __init__.py # 定义 package2 的接口
│ ├── feature.py # package2 核心功能
├── tests/
│ └── test_feature.py # 测试 package2 功能
├── setup.py # 配置分发信息
├── README.md # 项目说明文档
└── pyproject.toml # 构建工具配置
增加支持可选功能的目录结构
对于某些代码包(例如 vendor.package1
),可能需要进一步支持可选功能模块。此时可在原目录结构中增加子模块:
vendor-package1-repo/
├── src/
│ └── vendor/
│ └── package1/
│ ├── __init__.py # 定义主包的接口
│ ├── core.py # 主包核心功能
│ ├── optional1/
│ │ ├── __init__.py # optional1 的入口
│ │ └── feature1.py
│ └── optional2/
│ ├── __init__.py # optional2 的入口
│ └── feature2.py
├── tests/
│ ├── test_core.py # 测试主包功能
│ ├── optional1/
│ │ └── test_feature1.py # 测试 optional1
│ └── optional2/
│ └── test_feature2.py # 测试 optional2
├── setup.py # 配置分发信息
├── README.md # 项目说明文档
└── pyproject.toml # 构建工具配置
setup.py
配置
使用 setuptools
配置主包及其可选功能。通过 extras_require
定义可选功能模块及其依赖。
示例配置
from setuptools import setup, find_namespace_packages
setup(
name="vendor-package1", # 分发时的包名,符合 PyPI 命名规则
version="0.1.0",
description="Vendor Package1 with optional features",
author="Your Name",
author_email="your.email@example.com",
url="https://github.com/yourname/vendor-package1",
packages=find_namespace_packages(where="src"), # 自动发现命名空间包
package_dir={"": "src"}, # 指定 src 目录为包的根目录
python_requires=">=3.6",
install_requires=[
# 主包的必需依赖项
"some-core-library>=1.0",
],
extras_require={
"optional1": [
"optional-library1>=2.0", # optional1 所需的依赖
],
"optional2": [
"optional-library2>=3.0", # optional2 所需的依赖
],
"all": [
"optional-library1>=2.0",
"optional-library2>=3.0",
],
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
],
)
配置说明
extras_require
定义了可选功能及其依赖:optional1
和optional2
分别对应各功能模块的依赖。all
用于安装所有可选功能。
find_namespace_packages
自动发现命名空间包。package_dir
指定源码根目录为src
,避免包根目录污染。
主包入口设计
在 src/vendor/package1/__init__.py
中,通过 Facade 模式提供统一的入口,同时支持按需导入可选模块。
示例代码
# src/vendor/package1/__init__.py
# 导入主包核心功能
from .core import CoreClass
__all__ = ["CoreClass"]
# 尝试导入 optional1 和 optional2,如果未安装,则忽略
try:
from .optional1 import OptionalFeature1
__all__.append("OptionalFeature1")
except ImportError:
pass
try:
from .optional2 import OptionalFeature2
__all__.append("OptionalFeature2")
except ImportError:
pass
使用示例
用户安装主包后,可以直接使用核心功能:
from vendor.package1 import CoreClass
obj = CoreClass()
obj.some_method()
如果安装了 optional1
或 optional2
,可以按需导入:
from vendor.package1.optional1 import OptionalFeature1
from vendor.package1.optional2 import OptionalFeature2
feature1 = OptionalFeature1()
feature2 = OptionalFeature2()
测试策略
由于每个代码包及其可选功能均位于独立代码仓库中,并独立版本管理,因此测试需要考虑以下需求:
- 统一全量测试:测试主包功能以及所有可选功能。
- 部分功能测试:测试主包功能以及某个具体的可选功能。
- 版本兼容性测试:验证主包与不同版本的依赖包是否兼容。
示例测试代码
全量测试
在全量测试中,需要确保安装了所有可选功能的依赖:
pip install vendor-package1[all]
示例代码:
# tests/test_all.py
import unittest
from vendor.package1 import CoreClass
from vendor.package1.optional1 import OptionalFeature1
from vendor.package1.optional2 import OptionalFeature2
class TestAllFeatures(unittest.TestCase):
def test_core(self):
obj = CoreClass()
self.assertEqual(obj.some_method(), "expected result")
def test_optional1(self):
feature1 = OptionalFeature1()
self.assertTrue(feature1.some_method())
def test_optional2(self):
feature2 = OptionalFeature2()
self.assertTrue(feature2.some_method())
部分功能测试
只安装需要测试的功能依赖,例如:
pip install vendor-package1[optional1]
示例代码:
# tests/optional1/test_feature1.py
import unittest
from vendor.package1.optional1 import OptionalFeature1
class TestOptionalFeature1(unittest.TestCase):
def test_feature1(self):
feature1 = OptionalFeature1()
self.assertTrue(feature1.some_method())
版本兼容性测试
在测试环境中,明确安装需要测试的依赖版本,例如:
pip install vendor-package2==1.0.0
测试代码应验证功能是否正常运行:
# tests/test_version_compatibility.py
import unittest
from vendor.package1 import CoreClass
class TestVersionCompatibility(unittest.TestCase):
def test_with_specific_version(self):
obj = CoreClass()
self.assertTrue(obj.compatible_with_package2())
构建与分发
添加 MANIFEST.in
在 MANIFEST.in
文件中指定要包含的文件:
include README.md
include LICENSE
recursive-include src *
构建和上传到 PyPI
使用 build
工具构建并上传:
# 安装构建工具
pip install build twine
# 构建分发包
python -m build
# 上传到 PyPI
twine upload dist/*
用户安装与使用
安装主包
用户只需安装主包时:
pip install vendor-package1
安装特定可选功能
用户需要安装 optional1
或 optional2
时:
pip install vendor-package1[optional1]
pip install vendor-package1[optional2]
安装所有可选功能
一次性安装所有可选功能:
pip install vendor-package1[all]
注意事项
- 清晰的文档说明:在
README.md
中说明可选功能的用途及安装方法,帮助用户快速上手。 - 依赖管理:确保
extras_require
中的依赖版本与功能需求匹配。 - 版本兼容性:明确标注主包和可选模块之间的兼容版本关系,避免因版本不匹配导致问题。
- 向后兼容性:在
__init__.py
中使用try-except
捕获未安装的可选模块,避免因缺少依赖导致错误。
通过上述设计,vendor.package1
及其可选功能模块可以实现灵活的安装与使用,同时保证多个代码包间的独立性和兼容性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。