whl包构建
Posted 城南花已开
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了whl包构建相关的知识,希望对你有一定的参考价值。
安装依赖
pip install whell
pip install twine
参数对应
标注*号的为重要参数
描述性参数 —— 提供包信息,供PiPy识别管理
描述性参数,只是作为包的信息用的,没有特殊作用,可有可无。
参数 | 类型 | 说明 |
---|---|---|
*name | str | 包名称 |
*version | str | 包版本 |
*author | str | 程序的作者,这个包从头到尾都是你先开发的,那么你就是原作者。 |
*author_email | str | 程序的作者的邮箱地址 |
maintainer[^maintainer] | str | 维护者,如果你不是原作者,这个包是你修改原作者的包,那么你就是维护者。 |
maintainer_email | str | 维护者的邮箱地址 |
*url | str | 程序的官网地址 |
license | str | 程序的授权信息 |
description | str | 程序的简单描述 |
long_description | str | 程序的详细描述,详细描述在包上传PyPi后会在包页面下显示,支持RST和MD两种格式。 |
platforms | str | 程序适用的软件平台列表 |
classifiers | str | 程序的所属分类列表。影响上传PyPi会被分类到哪个类别。 |
keywords | str | 程序的关键字列表。同上。 |
download_url | str | 程序的下载地址 |
包文件搜集 —— 设置包的哪些文件需要打包,怎么找这些文件
参数 | 说明 |
---|---|
*package_dir | 用来给setuptools 指示packages 里的包名要从哪些目录下找,默认是setup.py所在的根目录下找packages。 |
*packages | 打包的包目录(通常为包含 init.py 的文件夹)。可以手动一个个包名添加,也可以直接用find_package 自动找指定目录下所有带 init.py的文件夹。默认只将文件夹内所有的.py 文件打包。如果文件夹内有其他类型文件,并且包依赖这些文件,需要通过设置package_data 来声明要打包的文件/类型。搜集的文件夹会被安装到site-packages 目录下。 |
*package_data | 指定包内需要包含的数据文件类型。默认packages只会把.py文件打包进去,如果包依赖其他类型文件就需要在package_data 里声明要打包进去的文件类型。 |
include_package_data | 如果设置为True,这将告诉setuptools自动将它找到的所有数据文件包含在MANIFEST.in文件指定的软件包目录中。MANIFEST.in可通过setuptool插件自动跟踪版本控制工具生成。 |
exclude_package_data | 将包名称映射到应该从包目录中排除的全局模式列表的字典。您可以使用它来修剪include_package_data包含的所有多余文件。有关完整的描述和示例,请参见“包括数据文件”部分。这个参数服务于include_package_data。 |
*py_modules | 需要打包的 Python 单文件列表。如果模块只是一个py文件,那么添加到这里打包进去。注意只需要写文件名,不要带.py后缀。 |
*data_files | 打包时需要打包的数据文件,如图片,配置文件等。格式("路径","文件"),路径是PYTHON_HOME目录下的相对路径。 |
scripts | 指定可执行脚本,安装时脚本会被安装到系统PATH路径下(PYTHON_HOME\\Scripts)。注意,一般是指命令行脚本,只有这种脚本安装到系统PATH路径下可以直接在命令行里调用。 |
依赖
参数 | 说明 |
---|---|
ext_modules | 指定c扩展模块 |
requires | 指定依赖的其他包。你的包依赖了其他外部包就添加到这里,安装你的包的时候会一并安装。(已过时,被install_requires代替了) |
install_requires | 安装时需要安装的依赖包,同上。 |
setup_requires | 指定运行 setup.py 文件本身所依赖的包。如果你在setup.py里面引用了依赖的包,就需要添加到这里。 |
extras_require | 当前包的高级/额外特性需要依赖的分发包。extras你可以理解为可选外部依赖,比如你的包有导出数据功能,默认支持csv格式,如果要导出excel格式则需要安装openxlrd,那么openxlrd就是可选外部依赖。 |
provides | 指定可以为哪些模块提供依赖。pip已经忽略这个参数了。 |
dependency_links | 指定依赖包的下载地址。pip已经不支持了。 |
打包项目
1、目录结构
2、创建setup.py
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages
try:
long_description = open("README.md").read()
except IOError:
long_description = ""
kwargs = {\'author\': \'Sucheon Algoritm Department\',
\'author_email\': \'haobin.zhang@sucheon.com\',
# 此处是把该文件打包到python的安装目录下的share目录中,如果share替换为Doc目录,并且文件替换为package.txt,则使用python -m package命令,会自动打印package.txt中的信息。
\'data_files\': [(\'share\',
[\'scdap_algorithm/function/other/sim_zs167/zsdl_model.pkl\'])],
\'description\': \'Sucheon Distributed Algorithm Processing System - Algorithm \'
\'Module.\',
# 此处搭配package_data参数,设置为True的时候,会把package_data中指定的包名下的文件进行上传
\'include_package_data\': True,
\'install_requires\': [\'numpy~=1.19.1\',
\'pandas==1.1.5\',
\'cython\',
\'statsmodels==0.12.2\',
\'scikit-learn~=0.21.3\',
\'scipy~=1.6.1\',
\'tensorflow==2.4.1\'],
\'license\': \'\',
\'long_description\': long_description,
\'long_description_content_type\': \'text/markdown\',
\'name\': \'scdap_algorithm\',
\'package_data\': {\'scdap_algorithm\': [\'*.pkl\']},
\'packages\': find_packages(),
\'python_requires\': \'>=3.7\',
\'version\': \'1.20211209.2009\'}
setup(**kwargs)
3、执行打包命令
python setup.py
上传whl包
python -m twine upload dist/*
示例代码
1、参数文件.whl.json
{
"desc": "算法包",
"module_name": "scdap_algorithm",
"lib_name": "scdap_algorithm",
"packages_parameter": {
"exclude": [
"function.decision",
"function.evaluation",
"function.other",
"function.decision.*",
"function.evaluation.*",
"function.other.*",
"lib",
"lib.*"
]
},
"package_data": {
"scdap_algorithm": [
"function/decision/__init__.py",
"function/evaluation/__init__.py",
"function/other/__init__.py",
"lib/__init__.py",
"function/decision/motor61.py",
"function/evaluation/hgear64.py",
"function/evaluation/hgear75.py",
"function/evaluation/hreducer88.py",
"*.pkl"
]
},
"install_requires": "reqirements.txt"
}
2、setup.py
"""
@author: 开花的马铃薯
@create on: 2021.03.22
package to wheels
.whl_parameters.json -> wheel包信息配置
{
"lib_name": "目标库目录路径名称",
"module_name": "需要打包成的库名称",
"package_data": {} // setup.package_data参数
"packages_parameter": { // setuptools.find_namespace_packages参数
"include": ("*",),
"exclude": ()
}
"version": "1.0.0", // 版本号, 在develop(测试环境下上传至pypi-develop)master环境下才上传至正式的pypi
"author": "xxx", // 作者
"author_email": "xxxx@xxx.com", // email
"install_requires": "requirements.txt" // 依赖文件路径
"description": "str", // 库的简要描述
"long_description": "README.md", // 完整的库说明文件路径
"long_description_content_type": "text/markdown", // 库说明文件类型
"python_requires": ">=3.7", // python版本配置
"license": "LICENSE" // LICENSE
...
}
"""
import os
import sys
import json
from pprint import pprint
from typing import Optional
from importlib import import_module
import requests
from twine import cli
from setuptools.extension import Extension
from setuptools import setup, find_namespace_packages
def get_requirements(path: str) -> list:
"""
解析依赖库信息
"""
requires = list()
if not os.path.exists(path):
return requires
with open(path, \'r\') as file:
for line in file.readlines():
if len(line) <= 1:
continue
if line[-1] == \'\\n\':
line = line[:-1]
requires.append(line)
return requires
def from_file(path: str):
if not os.path.exists(path):
return \'\'
with open(path, \'r\', encoding=\'utf-8\') as f:
return f.read()
def get_extensions(lib_dir: str, lib_name: str, exclude: list = ()):
packages = find_namespace_packages(lib_name)
packages = [f"{lib_name}.{path}" for path in packages]
packages.append(lib_name)
lib_dir = os.path.normpath(lib_dir)
extensions = []
for package in packages:
path = os.path.join(lib_dir, package.replace(\'.\', os.path.sep))
for fname in os.listdir(path):
simple_path = os.path.join(path, fname)
if fname.endswith(\'.py\') and fname not in exclude:
simple_package = f\'{package}.{os.path.splitext(fname)[0]}\'
# print(f\'{simple_package} -> {simple_path}\')
extensions.append(Extension(simple_package, [simple_path]))
return extensions
def get_parameter(setup_parameter: dict, setup_parameter_key: str,
module=None, module_key: str = None, default=None):
p = setup_parameter.get(setup_parameter_key)
if p is not None:
return p
if module is None or module_key is None:
if default is None:
print(f\'can not find {setup_parameter_key}.\')
raise SystemExit(1)
return default
p = getattr(module, module_key, default)
if p is None:
print(f\'can not find {setup_parameter_key} {module}.\')
raise SystemExit(1)
return p
def do_request(method: str, url: str, data: dict = None, token: str = \'\') -> Optional[dict]:
"""
:param method:
:param url:
:param data:
:param token:
:return:
"""
response = getattr(requests, method)(url, json=data, timeout=5, headers={\'Authorization\': token})
response.close()
if response.status_code != 200:
raise Exception(f\'sqlapi接口: {url}调用失败, http返回码为: {response.status_code}\')
response = response.json()
# 无法找到数据
if response[\'code\'] == \'B0100\':
print(f\'sqlapi接口: {url}调用失败, 返回码: {response["code"]}, 错误信息: {response.get("message")}\')
return None
if response[\'code\'] != \'00000\':
raise Exception(f\'sqlapi接口: {url}调用失败, 返回码: {response["code"]}, 错误信息: {response.get("message")}\')
return response[\'result\']
def package_wheel(setup_parameter: dict):
lib_name = setup_parameter[\'lib_name\']
module_name = setup_parameter.get(\'module_name\', lib_name)
try:
module = import_module(lib_name)
except Exception as e:
print(f\'can not import module: {lib_name}, error: {e}\')
raise SystemExit(1)
packages_parameter = setup_parameter.get(\'packages_parameter\', dict())
packages = find_namespace_packages(lib_name,
include=packages_parameter.get(\'include\', (\'*\',)),
exclude=packages_parameter.get(\'exclude\', ()))
packages = [f"{lib_name}.{path}" for path in packages]
packages.insert(0, lib_name)
sys.argv.extend([\'bdist_wheel\', \'-q\'])
kwargs = {
"name": module_name,
"packages": packages,
"include_package_data": True,
"data_files": [("share", ["scdap_algorithm/function/other/sim_zs167/zsdl_model.pkl"])],
"package_data": setup_parameter.get(\'package_data\', dict()),
"version": get_parameter(setup_parameter, \'version\', module, \'__version__\'),
"long_description": from_file(get_parameter(
setup_parameter, \'long_description\', default=\'README.md\')),
"long_description_content_type": get_parameter(
setup_parameter, \'long_description_content_type\', default="text/markdown"),
"license": from_file(
get_parameter(setup_parameter, \'license\', default=\'LICENSE\')),
"author": get_parameter(
setup_parameter, \'author\', module, "__author__", default=\'Sucheon Algoritm Department\'),
"author_email": get_parameter(
setup_parameter, \'author_email\', module, "__email__", default=\'haobin.zhang@sucheon.com\'),
"description": get_parameter(
setup_parameter, \'description\', module, "__description__", default=f\'Sucheon Algoritm Lib - {lib_name}\'),
"install_requires": get_requirements(
get_parameter(
setup_parameter, \'install_requires\', default=\'requirements.txt\')),
"python_requires": get_parameter(
setup_parameter, \'python_requires\', default=\'>=3.7\'),
}
pprint(kwargs)
setup(**kwargs)
return kwargs
def main(whl_parameter_path):
# 读取gitlab中的项目名称, 以解析成whl库名称
lib_name = os.environ.get(\'CI_PROJECT_NAME\')
if not lib_name:
lib_name = os.path.split(os.path.split(__file__)[0])[1]
lib_name = lib_name.lower().replace(\' \', \'_\').replace(\'-\', \'_\')
# 获取分支名称
# 用于区分多套环境
# develop -> 测试环境
# master -> 正式环境
env = os.environ.get(\'CI_COMMIT_REF_NAME\')
env_upper = env.upper()
# 根据环境获取对应的pypi服务器
twine_url = os.environ.get(f\'{env_upper}_TWINE_SERVER_URL\')
if twine_url is None:
print(F\'can not find env value: {env_upper}_TWINE_SERVER_URL\')
raise SystemExit(1)
print(\'twine server:\', twine_url)
user = os.environ.get(\'TWINE_SERVER_USER\')
if user is None:
print(\'can not find env value: TWINE_SERVER_USER\')
raise SystemExit(1)
user, password = user.split(\'/\')
# 根据环境获取对应的sqlapi服务
# 服务用于保存版本号至数据库
server_url = os.environ.get(f\'{env_upper}_SQLAPI_SERVER_URL\')
if server_url is None:
print(f\'can not find env value: {env_upper}_SQLAPI_SERVER_URL\')
raise SystemExit(1)
# sqlapi接口权限token
token = os.environ.get(\'SQLAPI_SERVER_TOKEN\', \'\')
# 读取模块打包的参数配置文件
print(f\'load file: {whl_parameter_path}\')
if os.path.exists(whl_parameter_path):
with open(whl_parameter_path, \'r\', encoding=\'utf-8\') as f:
whl_parameters = json.load(f)
# 如果加载的whl的json文件中没有该参数,就使用默认的值
if not whl_parameters.get("module_name"):
whl_parameters["lib_name"] = lib_name
else:
print(f\'can`t find file: {whl_parameter_path}\')
whl_parameters = {\'lib_name\': lib_name}
module_name = whl_parameters.get(\'module_name\', lib_name)
# 打包成whl
print(\'whl parameter:\')
pprint(whl_parameters)
whl_result = package_wheel(whl_parameters)
print(\'package module success.\')
print(\'module:\', list(os.listdir(\'dist/\')))
print(\'result module info:\')
pprint(whl_result)
# 上传whl至pypi
print(\'upload module wheel to twine.\')
cli.dispatch([\'upload\', \'--repository-url\', twine_url, \'-u\', user, \'-p\', password, \'dist/*\'])
print(\'upload module wheel success.\')
version = whl_result[\'version\']
url = f\'{server_url}/module-version/{module_name}/\'
result = do_request(\'get\', url, token=token)
print(\'old module version data:\')
pprint(result)
extra = dict()
old_version = \'null\'
if result:
extra = result[\'data\'][\'extra\']
old_version = result[\'data\'][\'version\']
data = {
\'module_name\': module_name,
\'version\': version,
\'description\': whl_result[\'description\'],
\'extra\': extra
}
extra[\'CI_COMMIT_SHA\'] = os.environ.get(\'CI_COMMIT_SHA\')
extra[\'CI_COMMIT_SHORT_SHA\'] = os.environ.get(\'CI_COMMIT_SHORT_SHA\')
extra[\'CI_COMMIT_REF_NAME\'] = os.environ.get(\'CI_COMMIT_REF_NAME\')
print(\'update module version data:\')
pprint(data)
# 更新版本号信息至数据库
if old_version == \'null\':
do_request(\'post\', url, data, token=token)
else:
do_request(\'put\', url, data, token=token)
if __name__ == \'__main__\':
# whl_parameters = dict()
# if os.path.exists(\'.tool_whl.json\'):
# with open(\'.tool_whl.json\', \'r\', encoding=\'utf-8\') as f:
# whl_parameters = json.load(f)
# package_wheel({"lib_name": "scdap_algorithm"})
if sys.argv[1:]:
pjson = sys.argv[-1]
sys.argv.pop(-1)
else:
pjson = \'.whl.json\'
main(pjson)
3、执行打包命令
python setup.py .whl.json
以上是关于whl包构建的主要内容,如果未能解决你的问题,请参考以下文章