whl包构建

摘要:
download_下载urlstr程序的地址包文件集合--设置需要打包包中的哪些文件,以及如何查找这些文件参数*package_目录用于向setuptools指示应该从哪个目录中找到包中的包名。*package package目录(通常是包含init.py的文件夹)。您也可以使用find directly_ package自动查找指定目录中包含init.py的所有文件夹。

安装依赖

pip install whell
pip install twine

参数对应

标注*号的为重要参数

描述性参数 —— 提供包信息,供PiPy识别管理

描述性参数,只是作为包的信息用的,没有特殊作用,可有可无。

参数类型说明
*namestr包名称
*versionstr包版本
*authorstr程序的作者,这个包从头到尾都是你先开发的,那么你就是原作者。
*author_emailstr程序的作者的邮箱地址
maintainer[^maintainer]str维护者,如果你不是原作者,这个包是你修改原作者的包,那么你就是维护者。
maintainer_emailstr维护者的邮箱地址
*urlstr程序的官网地址
licensestr程序的授权信息
descriptionstr程序的简单描述
long_descriptionstr程序的详细描述,详细描述在包上传PyPi后会在包页面下显示,支持RST和MD两种格式。
platformsstr程序适用的软件平台列表
classifiersstr程序的所属分类列表。影响上传PyPi会被分类到哪个类别。
keywordsstr程序的关键字列表。同上。
download_urlstr程序的下载地址

包文件搜集 —— 设置包的哪些文件需要打包,怎么找这些文件

参数说明
*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、目录结构

whl包构建第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包构建》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Android 项目中文件夹的说明与作用(转)div向右偏移设置 css让div靠右移一定距离下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

vue实现文件夹的上传和下载

需求: 支持大文件批量上传(20G)和下载,同时需要保证上传期间用户电脑不出现卡死等体验; 内网百兆网络上传速度为12MB/S 服务器内存占用低 支持文件夹上传,文件夹中的文件数量达到1万个以上,且包含层级结构。 支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保留进度信息...

ovn-kubernetes执行流程概述

Master部分 1、master初始化 以node name创建一个distributed logical router 创建两个load balancer用于处理east-west traffic,一个处理TCP,另一个处理UDP 创建一个名为"join"的logical switch用于连接gateway router和distributed ro...

创建TIff虚拟打印机

1.需要2个软件 GhostScript是PostScript解释程序,文件转换器。http://www.ghostscript.com/(下载地址: http://downloads.ghostscript.com/public/gs871w32.exe) RedMon是用于打印机端口监听。http://pages.cs.wisc.edu/~ghost...

ORA-00604的解决方法

分类: Oracle 从错误的角度可以推出:应该是表空间不足   根据查看表空间的使用情况: select b.file_name 物理文件名, b.tablespace_name 表空间, b.bytes/1024/1024 大小M, (b.bytes-sum(nvl(a.bytes,0)))/1024/1024 已使用M, substr((b.by...

Linux下使用VSCode开发OpenCV程序

在Linux下使用VSCode开发OpenCV程序,并使用cmake编译 创建项目 打开vscode,选择File->Open Folder VSCode配置 这里需要配置launch.json, tasks.json, c_cpp_properties.json三个文件; launch.json配置 点击左侧Debug, 选择Add Configu...

objdump命令

  转载于:http://man.linuxde.net/objdump        objdump命令是用查看目标文件或者可执行的目标文件的构成的gcc工具。 选项 --archive-headers -a 显示档案库的成员信息,类似ls -l将lib*.a的信息列出。 -b bfdname --target=bfdname 指定目标码...