安装依赖
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