Home Archives Categories Tags Docs

使用setup.py制作并发布Python包

发布时间: 更新时间: 总字数:5049 阅读时间:11m 作者: 分享

如何制作python的安装包?python模块的打包工具又有哪些?在OpenStack源码包中到底setup.py和setup.cfg是干什么的?

wheel egg 与 tgz介绍

首先我们从python包的格式说起,现在常见的包格式有egg, wheel以及源码安装包tgz,这三种格式的包都可以用setuptools进行制作。

egg格式的包在2004年通过setuptools引入,其本质上是一个包含了项目代码以及元数据(一个名为egg-info的子目录)的zip压缩包。

wheel格式则是于2012年在PEP 427中引入,一个Wheel格式的包中包含一个名为.dist-info的子目录。

.whl文件有一点与.egg文件相似:实际上它们都是“伪装的”.zip文件。如果你将.whl文件名扩展改为.zip,你就可以使用你的zip应用程序打开它,并且可以查看它包含的文件和文件夹。

egg格式的包需要使用easy_install进行安装, wheel以及tgz的都可以使用pip进行安装。 wheel也是官方推荐的包发布格式,相比于Egg与tgz,Wheel带来了许多的新特性,也显得更加友好。

安装pip环境准备

在制作whl包之前,我们需要先安装好pip,wheel。 2.7.9 以及3.4之后的python中默认已经安装了pip。如果没有安装pip,可以在此页面下载 get-pip.py.然后执行下面的命令安装

wget https://bootstrap.pypa.io/get-pip.py

[root@xiexianbin_cn py]# python get-pip.py
Collecting pip
  Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)
    100% |################################| 1.3MB 136kB/s 
Collecting wheel
  Downloading wheel-0.29.0-py2.py3-none-any.whl (66kB)
    100% |################################| 71kB 218kB/s 
Installing collected packages: pip, wheel
Successfully installed pip-9.0.1 wheel-0.29.0
[root@xiexianbin_cn py]# 

setup.py介绍

我们从例子开始。假设你要分发一个叫foo的模块,文件名foo.py、setup.py内容分别如下:

[root@xiexianbin_cn py]# ll
total 8
-rw-r--r-- 1 root root  13 Nov 15 22:07 foo.py
-rw-r--r-- 1 root root 106 Nov 15 22:07 setup.py
[root@xiexianbin_cn py]# cat foo.py 
print 'echo'
[root@xiexianbin_cn py]# cat setup.py 
from distutils.core import setup
setup(name='foo',
      version='1.0',
      py_modules=['foo'],
     )

然后,运行python setup.py sdist为模块创建一个源码包:

[root@xiexianbin_cn py]# python setup.py sdist
running sdist
running check
warning: check: missing required meta-data: url

warning: check: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) must be supplied

warning: sdist: manifest template 'MANIFEST.in' does not exist (using default file list)

warning: sdist: standard file not found: should have one of README, README.txt

writing manifest file 'MANIFEST'
creating foo-1.0
making hard links in foo-1.0...
hard linking foo.py -> foo-1.0
hard linking setup.py -> foo-1.0
creating dist
Creating tar archive
removing 'foo-1.0' (and everything under it)
[root@xiexianbin_cn py]# 

在当前目录下,会创建dist目录,里面有个文件名为foo-1.0.tar.gz,这个就是可以分发的包。使用者拿到这个包后,解压,到foo-1.0目录下执行:python setup.py install,那么,foo.py就会被拷贝到python类路径下,可以被导入使用。

[root@xiexianbin_cn py]# ll dist/
total 4
-rw-r--r-- 1 root root 399 Nov 15 22:08 foo-1.0.tar.gz
[root@xiexianbin_cn py]# cd dist/
[root@xiexianbin_cn dist]# tar -zxvf foo-1.0.tar.gz 
foo-1.0/
foo-1.0/foo.py
foo-1.0/PKG-INFO
foo-1.0/setup.py
[root@xiexianbin_cn dist]# cd foo-1.0/
[root@xiexianbin_cn foo-1.0]# python setup.py install
running install
running build
running build_py
creating build
creating build/lib
copying foo.py -> build/lib
running install_lib
copying build/lib/foo.py -> /usr/lib/python2.7/site-packages
byte-compiling /usr/lib/python2.7/site-packages/foo.py to foo.pyc
running install_egg_info
Writing /usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info
[root@xiexianbin_cn foo-1.0]# ll /usr/lib/python2.7/site-packages/foo   
foo-1.0-py2.7.egg-info  foo.py                  foo.pyc

对于Windows,可以执行python setup.py bdist_wininst生成一个exe文件;若要生成RPM包,执行python setup.py bdist_rpm,但系统必须有rpm命令的支持。可以运行下面的命令查看所有格式的支持:

[root@xiexianbin_cn py]# python setup.py bdist --help-formats
List of available distribution formats:
  --formats=rpm      RPM distribution
  --formats=gztar    gzip'ed tar file
  --formats=bztar    bzip2'ed tar file
  --formats=ztar     compressed tar file
  --formats=tar      tar file
  --formats=wininst  Windows executable installer
  --formats=zip      ZIP file
  --formats=msi      Microsoft Installer
[root@xiexianbin_cn py]# 

setup函数还有一些参数:

  1. packages

告诉Distutils需要处理那些包(包含init.py的文件夹)

  1. package_dir

告诉Distutils哪些目录下的文件被映射到哪个源码包。一个例子:package_dir = {”: ‘lib’},表示“root package”中的模块都在lib目录中。

  1. ext_modules

是一个包含Extension实例的列表,Extension的定义也有一些参数。

  1. ext_package

定义extension的相对路径

  1. requires

定义依赖哪些模块

  1. provides

定义可以为哪些模块提供依赖

  1. scripts

指定python源码文件,可以从命令行执行。在安装时指定–install-script

  1. package_data

通常包含与包实现相关的一些数据文件或类似于readme的文件。如果没有提供模板,会被添加到MANIFEST文件中。

  1. data_files

指定其他的一些文件(如配置文件)

setup(...,
      data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
                  ('config', ['cfg/data.cfg']),
                  ('/etc/init.d', ['init-script'])]
     )

规定了哪些文件被安装到哪些目录中。如果目录名是相对路径,则是相对于sys.prefix或sys.exec_prefix的路径。如果没有提供模板,会被添加到MANIFEST文件中。

执行sdist命令时,默认会打包哪些东西呢?

  • 所有由py_modules或packages指定的源码文件

  • 所有由ext_modules或libraries指定的C源码文件

  • 由scripts指定的脚本文件

  • 类似于test/test*.py的文件

  • README.txt或README,setup.py,setup.cfg

  • 所有package_data或data_files指定的文件

还有一种方式是写一个manifest template,名为MANIFEST.in,定义如何生成MANIFEST文件,内容就是需要包含在分发包中的文件。一个MANIFEST.in文件如下:

include *.txt
recursive-include examples *.txt *.py
prune examples/sample/build

python setup.py的其他帮助:

[root@xiexianbin_cn py]# python setup.py --help
Common commands: (see '--help-commands' for more)

  setup.py build      will build the package underneath 'build/'
  setup.py install    will install the package

Global options:
  --verbose (-v)      run verbosely (default)
  --quiet (-q)        run quietly (turns verbosity off)
  --dry-run (-n)      don't actually do anything
  --help (-h)         show detailed help message
  --no-user-cfg       ignore pydistutils.cfg in your home directory
  --command-packages  list of packages that provide distutils commands

Information display options (just display information, ignore any commands)
  --help-commands     list all available commands
  --name              print package name
  --version (-V)      print package version
  --fullname          print <package name>-<version>
  --author            print the author's name
  --author-email      print the author's email address
  --maintainer        print the maintainer's name
  --maintainer-email  print the maintainer's email address
  --contact           print the maintainer's name if known, else the author's
  --contact-email     print the maintainer's email address if known, else the
                      author's
  --url               print the URL for this package
  --license           print the license of the package
  --licence           alias for --license
  --description       print the package description
  --long-description  print the long package description
  --platforms         print the list of platforms
  --classifiers       print the list of classifiers
  --keywords          print the list of keywords
  --provides          print the list of packages/modules provided
  --requires          print the list of packages/modules required
  --obsoletes         print the list of packages/modules made obsolete

usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

[root@xiexianbin_cn py]# 

setup.cfg介绍

setup.cfg提供一种方式,可以让包的开发者提供命令的默认选项,同时为用户提供修改的机会。对setup.cfg的解析,是在setup.py之后,在命令行执行前。

setup.cfg文件的形式类似如下:

[command]
option=value
...

其中,command是Distutils的命令参数,option是参数选项,可以通过python setup.py –help build_ext方式获取。需要注意的是,比如一个选项是–foo-bar,在setup.cfg中必须改成foo_bar的格式

符合Distutils2的setup.cfg有些不同。包含一些sections:

  1. global

定义Distutils2的全局选项,可能包含commands,compilers,setup_hook(定义脚本,在setup.cfg被读取后执行,可以修改setup.cfg的配置)

  1. metadata

  2. files

  • packages_root:根目录

  • packages

  • modules

  • scripts

  • extra_files

  1. command sections


Setuptools

上面的setup.py和setup.cfg都是遵循python标准库中的Distutils,而setuptools工具针对Python官方的distutils做了很多针对性的功能增强,比如依赖检查,动态扩展等。很多高级功能我就不详述了,自己也没有用过,等用的时候再作补充。

一个典型的遵循setuptools的脚本:

from setuptools import setup, find_packages
setup(
    name = "HelloWorld",
    version = "0.1",
    packages = find_packages(),
    scripts = ['say_hello.py'],
    # Project uses reStructuredText, so ensure that the docutils get
    # installed or upgraded on the target machine
    install_requires = ['docutils>=0.3'],
    package_data = {
        # If any package contains *.txt or *.rst files, include them:
        '': ['*.txt', '*.rst'],
        # And include any *.msg files found in the 'hello' package, too:
        'hello': ['*.msg'],
    },
    # metadata for upload to PyPI
    author = "Me",
    author_email = "me@example.com",
    description = "This is an Example Package",
    license = "PSF",
    keywords = "hello world example examples",
    url = "http://example.com/HelloWorld/",   # project home page, if any
    # could also include long_description, download_url, classifiers, etc.
)

如何让一个egg可被执行?

setup(
    # other arguments here...
    entry_points = {
        'setuptools.installation': [
            'eggsecutable = my_package.some_module:main_func',
        ]
    }
)

如何定义一个可选特性?

setup(
    name="Project-A",
    ...
    extras_require = {
        'PDF':  ["ReportLab>=1.2", "RXP"],
        'reST': ["docutils>=0.3"],
    }
)

特性如何使用呢?需要与entry points结合使用:

setup(
    name="Project-A",
    ...
    entry_points = {
        'console_scripts': [
            'rst2pdf = project_a.tools.pdfgen [PDF]',
            'rst2html = project_a.tools.htmlgen',
            # more script entry points ...
        ],
    }
)

或者被其他project依赖:install_requires = [«Project-A[PDF]«]

插件式开发

我想大家最熟悉的就是这个特性了吧。比如一个博客系统想用不同的插件支持不同的语言输出格式,那么就可以定义一个“entry point group”,不同的插件就可以注册“entry point”,插件注册的示例:

setup(
    # ...
    entry_points = {'blogtool.parsers': ['.rst = some_module:a_func']}
)

或者

setup(
    # ...
    entry_points = """
        [blogtool.parsers]
        .rst = some.nested.module:SomeClass.some_classmethod [reST]
    """,
    extras_require = dict(reST = "Docutils>=0.3.5")
)

Setuptools中的dependency_links

Setuptools有一个功能叫做 dependency_links

from setuptools import setup

setup(
    # ...
    dependency_links = [
        "http://packages.example.com/snapshots/",
        "http://example2.com/p/bar-1.0.tar.gz",
    ],
)

这一功能除去了依赖的抽象特性,直接把依赖的获取url标在了setup.py里。就像在Go语言中修改依赖包一样,我们只需要修改依赖链中每个包的 dependency_links 。

管理依赖

我们写依赖声明的时候需要在 setup.py 中写好抽象依赖(install_requires),在 requirements.txt 中写好具体的依赖,但是我们并不想维护两份依赖文件,这样会让我们很难做好同步。 requirements.txt 可以更好地处理这种情况,我们可以在有 setup.py 的目录里写下一个这样的 requirements.txt

--index https://pypi.python.org/simple/
-e .

这样 pip install -r requirements.txt 可以照常工作,它会先安装该文件路径下的包,然后继续开始解析抽象依赖,结合 –index 选项后转换为具体依赖然后再安装他们。

这个办法可以让我们解决一种类似这样的情形:比如你有两个或两个以上的包在一起开发但是是分开发行的,或者说你有一个尚未发布的包并把它分成了几个部分。如果你的顶层的包 依然仅仅按照“名字”来依赖的话,我们依然可以使用 requirements.txt 来安装开发版本的依赖包:

--index https://pypi.python.org/simple/
-e https://github.com/foo/bar.git#egg=bar
-e .

这会首先从 https://github.com/foo/bar.Git 来安装包 bar , 然后进行到第二行 -e . ,开始安装 setup 中的抽象依赖,但是包 bar 已经安装过了, 所以 pip 会跳过安装。

Differences between distribute, distutils, setuptools and distutils2

Distutils is the standard tool used for packaging. It works rather well for simple needs, but is limited and not trivial to extend.

Setuptools is a project born from the desire to fill missing distutils functionality and explore new directions. In some subcommunities, it’s a de facto standard. It uses monkey-patching and magic that is frowned upon by Python core developers.

Distribute is a fork of Setuptools that was started by developers feeling that its development pace was too slow and that it was not possible to evolve it. Its development was considerably slowed when distutils2 was started by the same group. 2013-August update: distribute is merged back into setuptools and discontinued.

Distutils2 is a new distutils library, started as a fork of the distutils codebase, with good ideas taken from setup tools (of which some were thoroughly discussed in PEPs), and a basic installer inspired by pip. The actual name you use to import Distutils2 is packaging in the Python 3.3+ standard library, or distutils2 in 2.4+ and 3.1–3.2. (A backport will be available soon.) Distutils2 did not make the Python 3.3 release, and it was put on hold.

PBR

pbr是setuptools的辅助工具,最初是为OpenStack开发(https://launchpad.NET/pbr),基于d2to1。

A library for managing setuptools packaging needs in a consistent manner.

pbr会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给setup.py作为参数。包含如下功能:

  1. 从git中获取Version、AUTHORS and ChangeLog信息

  2. Sphinx Autodoc。pbr会扫描project,找到所有模块,生成stub files

  3. Requirements。pbr会读取requirements.txt,生成setup函数需要的install_requires/tests_require/dependency_links

这里需要注意,在requirements.txt文件的头部可以使用:–index https://pypi.python.org/simple/,这一行把一个抽象的依赖声明如 requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from pypi.python.org/simple/

  1. long_description。从README.rst, README.txt or README file中生成long_description参数

使用pbr很简单:

from setuptools import setup
setup(
    setup_requires=['pbr'],
    pbr=True,
)

使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key:

  • packages:指定需要包含的包,行为类似于setuptools.find_packages

  • namespace_packages:指定namespace packages

  • data_files: 指定目的目录和源文件路径,一个示例:

    [files]
    data_files =
    etc/pbr = etc/pbr/*
    etc/neutron =
    etc/api-paste.ini
    etc/dhcp-agent.ini
    etc/init.d = neutron.init

[entry_points]段跟setuptools的方式相同。

Babel

A collection of tools for internationalizing Python applications

Babel是 Python 的一个国际化工具包,提供了对distutils或setuptools的支持,包含一些命令。

  1. compile_catalog

类似于msgfmt工具,takes a message catalog from a PO file and compiles it to a binary MO file.

$ ./setup.py compile_catalog --directory foobar/locale --locale pt_BR
running compile_catalog
compiling catalog to foobar/locale/pt_BR/LC_MESSAGES/messages.mo
  1. extract_messages

类似于xgettext,it can extract localizable messages from a variety of difference source files, and generate a PO (portable object) template file from the collected messages.

$ ./setup.py extract_messages --output-file foobar/locale/messages.pot
running extract_messages
extracting messages from foobar/__init__.py
extracting messages from foobar/core.py
...
writing PO template file to foobar/locale/messages.pot
  1. update_catalog

类似于msgmerge,it updates an existing translations catalog based on a PO template file (POT).

setup.py和pip

表面上,python setup.py install和pip install都是用来安装python包的,实际上,pip提供了更多的特性,更易于使用。体现在以下几个方面:

  • pip会自动下载依赖,而如果使用setup.py,则需要手动搜索和下载;

  • pip会自动管理包的信息,使卸载/更新更加方便和容易,使用pip uninstall即可。而使用setup.py,必须手动删除,有时容易出错。

  • pip提供了对virtualenv更好的整合。

上传到pypi

制作好的python发行包只有发布到了互联网上才能被其他人下载使用。首先需要到此目录注册一个帐号https://pypi.python.org/pypi。

然后安装twine,twine是一个安全的包发布工具

pip install twine

然后注册你的python包

twine register dist/mypkg-0.1-py2.py3-none-any.whl

最后一步就是上传你的包到pypi上

twine upload dist/*

至此,大功告成,你已完成了一个python包的制作与发布。

参考

相关文章
最近更新
最新评论
加载中...