setuptools

setuptools

SetuptoolsPython Distutils的加强版,使开发者构建和发布Python包更加容易,特别是当包依赖于其他包时。用setuptools构建和发布的包与用Distutils发布的包是类似的。包的使用者无需安装setuptools就可以使用该包。如果用户是从源码包开始构建,并且没有安装过setuptools的话,则只要在你的setup脚本中包含一个bootstrap模块(ez_setup,用户构建时就会自动下载并安装setuptools了。

$ pip install --upgrade setuptools

基础用例

from setuptools import setup, find_packages
setup(
    name = "HelloWorld",
    version = "0.1",
    packages = find_packages(),
)

上面就是一个最简单的setup脚本,使用该脚本,就可以产生eggs,上传PyPI,自动包含setup.py所在目录中的所有包等。当然,上面的脚本过于简单,下面是一个稍微复杂的例子:

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.
)

工具函数

find_packages

对于简单的工程,使用setup函数的packages参数一一列出安装的包到就足够了。但是对于大型工程来说,这却有点麻烦,因此就有了setuptools.find_package()函数。find_packages的参数有:一个源码目录,一个include包名列表,一个exclude包名列表。如果这些参数被忽略,则源码目录默认是setup.py脚本所在目录。该函数返回一个列表,可以赋值给packages参数。

有些工程可能会使用src或者lib目录作为源码树的子目录,因此这些工程中,需要使用”src”或者”lib”作为find_packages()的第一个参数,当然,这种情况下还需要设置package_dir = {’’:’lib’},否则的话会报错,比如setup脚本如下:

from setuptools import setup, find_packages
setup(
    name = "HelloWorld",
    version = "0.1",
    package_dir = {'':'lib'},
    packages = find_packages('lib'),
)

源码树如下:

lib/
    foo.py
    heheinit.py
    bar/
        __init__.py
        bar.py

最终生成的文件如下:

/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/dependency_links.txt

/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/PKG-INFO

/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/SOURCES.txt

/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/top_level.txt

/usr/local/lib/python2.7/dist-packages/bar/bar.py

/usr/local/lib/python2.7/dist-packages/bar/bar.pyc

/usr/local/lib/python2.7/dist-packages/bar/__init__.py

/usr/local/lib/python2.7/dist-packages/bar/__init__.pyc

如果没有 package_dir = {'':'lib'} 的话,则会报错:

error: package directory 'bar' does not exist

这是因为执行函数find_packages(’lib’),返回的结果是[‘bar’],没有package_dir = {’’:’lib’}的话,则在setup.py所在目录寻找包bar,自然是找不到的了。

>>> import setuptools
>>> setuptools.find_packages('lib')
['bar']

find_packages()函数遍历目标目录,根据include参数进行过滤,寻找Python包。对于Python 3.2以及之前的版本,只有包含 __init__.py 文件的目录才会被当做包。最后,对得到的结果进行过滤,去掉匹配exclude参数的包。includeexclude参数是包名的列表,包名中的’.’表示父子关系。比如,如果源码树如下:

lib/
    foo.py
    __init__.py
    bar/
        __init__.py
        bar.py

find_packages(exclude=[“lib”])(或packages = find_packages(include=[“lib”]),只是排除(或包含)lib包,但是却不会排除(或包含lib.bar)包。

entry points

entry points是发布模块“宣传”Python对象(比如函数、类)的一种方法,这些Python对象可以被其他发布模块使用。一些可扩展的应用和框架可以通过特定的名字找到entry points,也可以通过发布模块的名字来找到,找到之后即可加载使用这些对象了。entry points要属于某个entry points组,组其实就是一个命名空间。在同一个entry point组内不能有相同的entry point

entry points通过setup函数的entry_points参数来表示,这样安装发布包之后,发布包的元数据中就会包含entry points的信息。entry points可以实现动态发现和执行插件,自动生成可执行脚本、生成可执行的egg文件等功能。setup函数的entry_points参数,可以是INI形式的字符串,也可以是一个字典,字典的keyentry point group的名字,value是定义entry point的字符串或者列表。

一个entry point就是 name = value 形式的字符串,其中的value就是某个模块中对象的名字。另外,在”name = value”中还可以包含一个列表,表示该entry point需要用到的”extras”,当调用应用或者框架动态加载一个entry point的时候”extras”表示的依赖包就会传递给pkg_resources.require()函数,因此如果依赖包没有安装的话就会打印出相应的错误信息。

比如entry_points可以这样写:

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

setup(
    ...
    entry_points = {'blogtool.parsers': '.rst = some_module:SomeClass[reST]'}
    extras_require = dict(reST = "Docutils>=0.3.5")
    ...
)

setup(
    ...
    entry_points = {'blogtool.parsers': ['.rst = some_module:a_func[reST]']}
    extras_require = dict(reST = "Docutils>=0.3.5")
    ...
)
上一页
下一页