• 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
    
  • 相关阅读:
    PVT--无卷积密集预测的多功能backbone
    MobileNet系列之MobileNet_v3
    MobileNet系列之MobileNet_v2
    CVPR2021 | 开放世界的目标检测
    Siamese network总结
    文字识别OCR开源框架的对比--Tesseract vs EasyOCR
    CVPR2021|一个高效的金字塔切分注意力模块PSA
    Boltdb学习笔记之二--数据结构
    C++关键字之likely和unlikely
    Boltdb学习笔记之〇--概述
  • 原文地址:https://www.cnblogs.com/cnhyk/p/15669236.html
Copyright © 2020-2023  润新知