[Python] 如何打包成 Package

程式語言:Python
Package:setuptools
PyPA 文件
setuptools 官網文件
PyPA sample project

功能:打包為 package 以便分享

基本架構

MyProject-|--setup.py    => 最重要的文件,包含了打包的參數和基本信息
          |--setup.cfg   => setup.py 的命令配置文件,其格式為 INI
          |--README.rst  => 介紹,格式若為 README.md 需額外設置 MANIFEST.in 打包此文件
          |--MANIFEST.in => 指定需額外打包的文件
          |--LICENSE.txt
          |--pkgA---|--__init__.py
          |         |--modA_1.py 
          |         |--modA_2.py
          |--pkgB---|--__init__.py
                    |--modB.py

setup.py

必要文件,包括各種參數與訊息
可用 python setup.py --help-commands 獲得相關指令
"""A setuptools based setup module.

See:
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""

# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
    long_description = f.read()

# Arguments marked as "Required" below must be included for upload to PyPI.
# Fields marked as "Optional" may be commented out.

setup(
    ....
)
  • 必要參數
    • name
      • project 的名字
    • version
      • 版本,命名方式
        1.2.0.dev1  # Development release
        1.2.0a1     # Alpha Release
        1.2.0b1     # Beta Release
        1.2.0rc1    # Release Candidate
        1.2.0       # Final Release
        1.2.0.post1 # Post Release
        15.10       # Date based release
        23          # Serial release
        
    • description
      • 簡介
    • packages
      • 預計打包的 package
      • 可使用 setuptools.find_packages() 自動尋找,並可指定排除
        packages=find_packages(exclude=['contrib', 'docs', 'tests'])
        

  • 可選參數
    • long_description
      • 詳細說明,通常與 'README.rst' 一樣,可直接讀取
        with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
            long_description = f.read()
        
        long_description=long_description,
        
    • long_description_content_type
      • 內容格式
        • text/plain
        • text/x-rst
        • text/markdown
    • url
      • project 網址
    • author
      • 作者
    • author_email
      • 作者信箱
    • license
      • 授權
    • classifiers
      • 分類資訊,支援類型
        classifiers=[  # Optional
            # How mature is this project? Common values are
            #   3 - Alpha
            #   4 - Beta
            #   5 - Production/Stable
            'Development Status :: 3 - Alpha',
        
            # Indicate who your project is intended for
            'Intended Audience :: Developers',
            'Topic :: Software Development :: Build Tools',
        
            # Pick your license as you wish
            'License :: OSI Approved :: MIT License',
        
            # Specify the Python versions you support here. In particular, ensure
            # that you indicate whether you support Python 2, Python 3 or both.
            'Programming Language :: Python :: 2',
            'Programming Language :: Python :: 2.7',
            'Programming Language :: Python :: 3',
            'Programming Language :: Python :: 3.4',
            'Programming Language :: Python :: 3.5',
            'Programming Language :: Python :: 3.6',
        ]
        
    • keywords
      • 關鍵字
        keywords='sample setuptools development'
        
    • project_urls
      • 額外的連結
        project_urls={
            'Bug Reports': 'https://github.com/pypa/sampleproject/issues',
            'Funding': 'https://donate.pypi.org',
            'Say Thanks!': 'http://saythanks.io/to/example',
            'Source': 'https://github.com/pypa/sampleproject/',
        }
        
    • install_requires
      • 依賴的 package
        install_requires=['numpy, matplotlib'],
        
    • python_requires
      • 依賴特定版本的 python
        # 版本需大於 3
        python_requires='>=3',
        # 版本需為 3.3
        python_requires='~=3.3',
        # 版本需為 2.6, 2.7 或 從 3.3 開始
        python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4 pre="">
        
    • package_data
      • 需打包的額外文件,使用方式
        package_data={
            'sample': ['package_data.dat'],
        }
        
    • data_files
      • 不在 package 中,但需打包的文件,安裝在 sys.prefix 下,使用方式
        # In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
        data_files=[('my_data', ['data/data_file'])]
        
    • scripts
      • 預打包的自定 scripts,但建議使用 console_scripts
        scripts=['bin/packagedemo_cli']
        
    • py_modules
      • 預計打包的 module
        py_modules=["my_module"]
        
    • entry_points
      • 預計支援的 plugin
        entry_points={
            'console_scripts': [
                'sample=sample:main',
            ],
        }
        
    • extras_require
      • 特殊功能,需用到的 package,不會主動安裝
        extras_require={
            'dev': ['check-manifest'],
            'test': ['coverage'],
        }
        
    • include_package_data
      • 是否有其他的打包文件
      • 設為 True 可在 MANIFEST.in 設定

setup.cfg

在 setup.py 設定的值,皆可透過此文件設定,命令的預設參數也可指定,設定方法
[metadata]
name = my_package
version = attr: src.VERSION
description = My package description
long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst
keywords = one, two
license = BSD 3-Clause License
classifiers =
    Framework :: Django
    Programming Language :: Python :: 3
    Programming Language :: Python :: 3.5

[options]
zip_safe = False
include_package_data = True
packages = find:
scripts =
  bin/first.py
  bin/second.py

[options.package_data]
* = *.txt, *.rst
hello = *.msg

[options.extras_require]
pdf = ReportLab>=1.2; RXP
rest = docutils>=0.3; pack ==1.1, ==1.3

[options.packages.find]
exclude =
    src.subpackage1
    src.subpackage2
[bdist_wheel]
# This flag says to generate wheels that support both Python 2 and Python
# 3. If your code will not run unchanged on both Python 2 and 3, you will
# need to generate separate wheels for each Python version that you
# support.
universal=1

MANIFEST.in

設定方法
需在 setup.py 設定 include_package_data=True,才會有作用
include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/build

打包方法

兩種方式,打包後的檔案,皆放在 dist 下

Source Distributions (sdist) 原始碼
python setup.py sdist

Wheel (建議使用)
需先安裝 wheel
python -m pip install wheel
python setup.py bdist_wheel
P.S. 
即使使用 MANIFEST.in 指定,在 package 外的文件仍不會被打包,例:LICENSE.txt
此時需 data_files 指定才行,但 sdist 並不會有此問題

上傳至 PyPI

可同時上傳 原始碼 與 wheel,pip install 時將優先使用 wheel
  1. 註冊帳號 
  2. 安裝 twine
    pip install twine
    
  3. 上傳至測試網站,確認內容正確
    # 上傳測試
    twine upload --repository-url https://test.pypi.org/legacy/ dist/*
    # 安裝測試
    pip install --index-url https://test.pypi.org/simple/ your-package
    
  4. 上傳至 PyPI
    twine upload dist/*
    
若不想常常輸入密碼
可建立 $HOME/.pypirc 文件
[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username: <username>
password: <password>

[testpypi]
repository: https://test.pypi.org/legacy/
username: your testpypi username
password: your testpypi password

參考

Python application 的打包和发布——(上)
PyPI, pip, easy_install, setuptools,distutils,egg 等等
一文教会你正确打包Python程序
Python打包分发工具setuptools简介

留言

  1. 版本命名其實挺重要...pip install --pre會影響到能不能安裝(pep426)

    回覆刪除

張貼留言