スタートガイド

新たなツールに慣れる最も簡単な方法は実際に実行できるサンプルを見てみることです。つまり、どのように Paver を使い始めれば良いかということになります。Paver ディストリビューション内の docs/sample にサンプルがあります。そして、その “started” ディレクトリにスタートガイドのサンプルがあります。

旧い方法

最初のサンプルは “旧い方法” と呼びます(docs/samples/started/oldway ディレクトリにあります)。それは配布できるようにしたい Python パッケージとドキュメントの適正且つ典型的なプロジェクトです。

Python の distutils を使用すると配布パッケージをとても簡単に作成できます。次のような setup.py ファイルを作成します。

#<== include('started/oldway/setup.py')==>
from distutils.core import setup

setup(
    name="TheOldWay",
    packages=['oldway'],
    version="1.0",
    url="http://www.blueskyonmars.com/",
    author="Kevin Dangoor",
    author_email="dangoor@gmail.com"
)
#<==end==>

簡単な setup スクリプトで実行します。

python setup.py sdist

ソースディストリビューションをビルドするには、

# <==
# sh('cd docs/samples/started/oldway; python setup.py sdist',
#    insert_output=False)
# sh('ls -l docs/samples/started/oldway/dist')
# ==>
celkem 4
-rw-r--r-- 1 almad almad 629 11. lis 22.19 TheOldWay-1.0.tar.gz
# <==end==>

それからユーザは見慣れたコマンドを実行します。

python setup.py install

パッケージをインストールするために setuptools を使用するとさらに簡単です。

easy_install "TheOldWay"

パッケージは Python パッケージインデックスにあります。

旧い方法のドキュメント

旧い方法プロジェクトはドキュメントに Sphinx を使用しているので少なくともちょっとはモダンです。sphinx-quickstart を使用してドキュメントを作成し始めるとき Sphinx は HTML ドキュメントを生成する Makefile を設けるでしょう。そのため、HTML ドキュメントを簡単に生成できます。

make html

但し、(Paver そのもののように)このプロジェクトでは、ユーザへヘルプを定期ょ湯するためにパッケージの docs ディレクトリに HTML ファイルを追加したいです。最終的に次のようなシェルスクリプトを作ります。

# <== include("started/oldway/builddocs.sh")==>
cd docs
make html
cd ..
rm -rf oldway/docs
mv docs/_build/html oldway/docs
# <==end==>

もちろん、このようなスクリプトを作成することは、実際にそのスクリプトの実行を覚えておく必要があることを意味します。このスクリプトを “buildsdist.sh” に変更して、ファイルの最後に python setup.py sdist を追加することはできます。しかし、直接 python setup.py sdist を実行できればもっと良い気がしますよね?

新たな distutils コマンドを作成する こともできますが、Python ライブラリディレクトリの distutils/command パッケージにそんな機能を本当に追加したいでしょうか?そして、いずれにしても sdist コマンドをどうやって呼び出すのでしょうか? setuptools は助けてくれますが、それでもこのコマンドのコレクションのためにエントリポイントとモジュールのセットアップが必要になります。

私のところでは動作する

これを読んで “うわっ、なんて不自然なサンプルだ!” と考える人がいることも 分かります 。プロジェクトのビルド、パッケージング、配布、デプロイは全てのプロジェクトでかなりカスタマイズされます。Paver の重要なことの1つはプロジェクトでどんな奇妙な要求が起こっても簡単に扱えるようにすることです。このサンプルは不自然に見えるかもしれませんが、どうやって Paver がそういったタスクを簡単に扱うかについてのアイディアを説明します。

新しい方法

これまでのセクションのスクリプティングを今すぐクリーンアップして Paver を少し取り入れてみましょう。Paver を使用するために既存のプロジェクトから移行することは、本当に、本当に簡単です。旧い方法の setup.py から setup() 関数を呼び出してください。

# <== include("started/oldway/setup.py", "setup")==>
setup(
    name="TheOldWay",
    packages=['oldway'],
    version="1.0",
    url="http://www.blueskyonmars.com/",
    author="Kevin Dangoor",
    author_email="dangoor@gmail.com"
)
# <==end==>

Paver を使い始める

setup.py は普通の Python スクリプトです。それは慣例として setup.py と呼ばれます。Paver は Make や Rake のように動作します。Paver を使用するために paver <taskname> を実行します。そして paver コマンドはカレントディレクトリの pavement.py ファイルを探します。pavement.py は普通の Python モジュールです。典型的な pavement.py は paver.easy から便利な関数、オブジェクトのセットをインポートします。それから役に立つタスクを含むその他のモジュールをインポートします。

# <== include('started/newway/pavement.py', 'imports')==>
from paver.easy import *
import paver.doctools
from paver.setuputils import setup
# <==end==>

setup.py から pavement.py に変換することは簡単です。Paver は全てのビルドオプションを含む特殊な options オブジェクトを提供します。 options は属性スタイルのアクセスを許可して特別な検索機能を持つ辞書です。distutils の操作オプションはそのオプションの setup セクションに格納されます。そして、慣例として Paver はその options セクションに値をセットする setup 関数を提供します(さらに一歩踏み込むと、全ての distutils/setuptools コマンドを作ることで Paver のタスクとして利用できます)。ここにその移行がどのように行うかを紹介します。

# <== include('started/newway/pavement.py', 'setup')==>
setup(
    name="TheNewWay",
    packages=['newway'],
    version="1.0",
    url="http://www.blueskyonmars.com/",
    author="Kevin Dangoor",
    author_email="dangoor@gmail.com"
)
# <==end==>

Paver は distutils 互換

Paver を選択するということは distutils や setuptools を使用しないということではありません。Paver は distutils や setuptools コマンドを使用し続けます。Paver のタスクを持つモジュールをインポートするとき、それらのタスクは実行時に自動的に利用可能になります。distutils や setuptools に対して同様にアクセスしたいなら、上述したように paver.setuputils.setup を使用するか paver.setuputils.install_distutils_tasks() を呼び出すか、どちらでも使用することができます。

実際に paver help を実行すると見ることができます。

# <== sh('cd docs/samples/started/newway; paver help')==>
Could not load entry point: upload_docs = setuptools.command.upload_docs:upload_docs
---> paver.tasks.help
Usage: paver [global options] taskname [task options] [taskname [taskoptions]]

Options:
  --version             show program's version number and exit
  -n, --dry-run         don't actually do anything
  -v, --verbose         display all logging output
  -q, --quiet           display only errors
  -i, --interactive     enable prompting
  -f FILE, --file=FILE  read tasks from FILE [pavement.py]
  -h, --help            display this help information

Tasks from distutils.command:
  bdist            - create a built (binary) distribution
  bdist_dumb       - create a "dumb" built distribution
  build            - build everything needed to install
  build_clib       - build C/C++ libraries used by Python extensions
  build_scripts    - "build" scripts (copy and fixup #! line)
  clean            - clean up temporary files from 'build' command
  install_data     - install data files
  install_headers  - install C/C++ header files
  upload           - upload binary package to PyPI

Tasks from nose.commands:
  nosetests        - Run unit tests using nosetests

Tasks from paver.doctools:
  cog              - Runs the cog code generator against the files matching your
    specification
  doc_clean        - Clean (delete) the built docs
  html             - Build HTML documentation using Sphinx
  uncog            - Remove the Cog generated code from files

Tasks from paver.misctasks:
  generate_setup   - Generates a setup
  minilib          - Create a Paver mini library that contains enough for a simple
    pavement

Tasks from paver.tasks:
  help             - This help display

Tasks from setuptools.command:
  alias            - define a shortcut to invoke one or more commands
  bdist_egg        - create an "egg" distribution
  bdist_rpm        - create an RPM distribution
  bdist_wininst    - create an executable installer for MS Windows
  build_ext        - build C/C++ and Pyrex extensions (compile/link to build directory)
  build_py         - "build" pure Python modules (copy to build directory)
  develop          - install package in 'development mode'
  easy_install     - Find/get/install Python packages
  egg_info         - create a distribution's .egg-info directory
  install          - install everything from build directory
  install_egg_info - Install an .egg-info directory for the package
  install_lib      - install all Python modules (extensions and pure Python)
  install_scripts  - install scripts (Python or otherwise)
  register         - register the distribution with the Python package index
  rotate           - delete older distributions, keeping N newest files
  saveopts         - save supplied options to setup.cfg or other config file
  sdist            - create a source distribution (tarball, zip file, etc.)
  setopt           - set an option in setup.cfg or another config file
  test             - run unit tests after in-place build

Tasks from sphinx.setup_command:
  build_sphinx     - Build Sphinx documentation

Tasks from pavement:
  deploy           - Deploy the HTML to the server
  html             - Build the docs and put them into our package
  sdist            - Overrides sdist to make sure that our setup
# <==end==>

ヘルプコマンドは利用可能な全てのタスクを表示します。その上部付近に distutils.command からのタスクがあります。標準の distutils の全コマンドが利用できます。

Python パッケージが適切に再配布可能になる前に行う必要があることが1つあります。distutils に特別なファイルを伝えます。それは簡単な MANIFEST.in で実行できます。

# <== include('started/newway/MANIFEST.in')==>
include setup.py
include pavement.py
include paver-minilib.zip
# <==end==>

そのファイルを使用して paver sdist を実行すると、最終的に等価な出力ファイルが生成されます。

# <==
# sh('cd docs/samples/started/newway; paver sdist',
#    insert_output=False)
# sh('ls -l docs/samples/started/newway/dist')
# ==>
celkem 32
-rw-r--r-- 1 almad almad 28860 11. lis 22.19 TheNewWay-1.0.tar.gz
# <==end==>

新しい方法プロジェクトのユーザはシステム上にパッケージをインストールするために paver install も実行できることにもなります。

しかし人は setup.py を使用する!

python setup.py install は長い間使われてきました。 paver install を実行することをユーザへ伝えるためにパッケージ内に必ず README を置くようにしますが、誰も実際にはそのドキュメントを読まないことを私たちみんなが知っています。(そうそう、このガイドを読む時間を取ってくれてありがとう!)

そうだとしても心配しないで。tarball の中に入れる setup.py ファイルを生成するために paver generate_setup を実行することができます。それから、ユーザは慣れた方法で python setup.py install を実行して Paver がその処理を引き継ぎます。

しかし人はまだ Paver を持っていない!

まだ Paver を持っていない Python のインストール方法は何百万もありますが Python と distutils は持っています。どうやって Pathon ベースのインストールが実行されるだろうか?

それは簡単です、 paver minilib を単に実行すると paver-minilib.zip というファイルが作られます。そのファイルには多くのプロジェクトをインストールするために Paver の十分な機能を持っています。Paver が生成した setup.py はそのファイルを探すようになっていて、見つかったらそのファイルを使用します。

あなたのパッケージの肥大化が心配? paver-minilib は大きくありません。

# <==
# sh('cd docs/samples/started/newway ; paver minilib',
#    insert_output=False)
# sh('ls -l docs/samples/started/newway/paver-minilib.zip')
# ==>
-rw-r--r-- 1 almad almad 27309 11. lis 22.19 docs/samples/started/newway/paver-minilib.zip
# <==end==>

Paver そのものは生成された setup ファイルと paver-minilib でブートストラップされます。

やぁ!あなたは私のためにもっと作業を作らないの?

新しい方法プロジェクトの適正なディストリビューションを作るために3つのコマンドが実行できることに気付いたかもしれません。えっと、実際に全てのコマンド paver generate_setup minilib sdist を同時に実行することはできます。しかし、それは思うほどひどい方法ではありませんが、良い方法でもありません。実際にはあなたはそのタスクのどれかを忘れるので、そのことにより最終的に壊れたディストリビューションを作りたくはないと思います。

意図的に、Paver で実行する最も簡単な方法の1つは既存の “タスク” の処理を拡張して distutils コマンドを含めるようにします。必要な全ての処理を pavement.py の新たな sdist タスクとして作成することです。

# <== include('started/newway/pavement.py', 'sdist')==>
@task
@needs('generate_setup', 'minilib', 'setuptools.command.sdist')
def sdist():
    """Overrides sdist to make sure that our setup.py is generated."""
    pass
# <==end==>

@task デコレータは Paver へこれはタスクであって関数ではないことを伝えます。@needs デコレータはこのタスクが実行される前に実行すべき他のタスクを指定します。お好みでタスク内で call_task(taskname) 関数を呼び出すこともできます。その関数名はタスクの名前を決定します。docstring は Paver のヘルプに出力される内容になります。

上述した pavement.py のタスクで setup ファイルと minilib を生成した後でソースディストリビューションをビルドするために必要なコマンドは paver sdist だけです。

ドキュメントに立ち向かう

そのツールそのものが pavement を簡単に作成するタスクと関数を提供するまで Paver の標準ライブラリは共通ツールとして役立つモジュールのコレクションを追加します。Sphinx は Paver がビルトインサポートしているパッケージの1つです。

Paver の Sphinx サポートを使用するために Sphinx をインストールする必要があります。そして pavement.py に import paver.doctools を追加します。インポートを実行するだけで doctools 関連のタスクが利用できます。 paver help html を実行すると html コマンドの使用方法を表示します。

# <== sh('paver help paver.doctools.html')==>
Could not load entry point: upload_docs = setuptools.command.upload_docs:upload_docs
---> paver.tasks.help

paver.doctools.html
-------------------
Usage: paver paver.doctools.html [options]

Options:
  -h, --help  display this help information

Build HTML documentation using Sphinx. This uses the following
    options in a "sphinx" section of the options.

    docroot
      the root under which Sphinx will be working. Default: docs
    builddir
      directory under the docroot where the resulting files are put.
      default: build
    sourcedir
      directory under the docroot for the source files
      default: (empty string)


# <==end==>

表示されたヘルプによると “_build” という builddir を使用するので builddir を設定する必要があります。pavement.py にこの設定を追加してみましょう。

# <== include('started/newway/pavement.py', 'sphinx')==>
options(
    sphinx=Bunch(
        builddir="_build"
    )
)
# <==end==>

これにより paver html は Sphinx が持っていた Makefile を使用して make html を実行することと等価になります。

ドキュメントからシェルスクリプトを取り除く

生成したドキュメントを適切な場所へ移動するシェルスクリプトを覚えておいた方が良いです。

# <== include('started/oldway/builddocs.sh')==>
cd docs
make html
cd ..
rm -rf oldway/docs
mv docs/_build/html oldway/docs
# <==end==>

理想的には、ドキュメントを生成するときはこのように実行したいです。タスクをオーバーライドする方法は既に確認したので、ここでそれをやってみましょう。

# <== include('started/newway/pavement.py', 'html')==>
@task
@needs('paver.doctools.html')
def html(options):
    """Build the docs and put them into our package."""
    destdir = path('newway/docs')
    destdir.rmtree()
    builtdocs = path("docs") / options.builddir / "html"
    builtdocs.move(destdir)
# <==end==>

この設定には少し興味深いことがあります。オーバーライドしたタスクを使用するので ‘make html’ と @needs(‘paver.doctools.html’) は等価になります。

タスク内部では “path” を使用しています。これは Jason Orendorff がカスタマイズした path モジュールです。どのようなファイルやディレクトリ操作もこのモジュールを使用すると超簡単になります。

新たに生成したファイルを対象ディレクトリにコピーするので、先ずはそのディレクトリを削除することから始めます。次に移動するためのビルドした docs ディレクトリを探します。

# <== include('started/newway/pavement.py', 'html.builtdocs')==>
builtdocs = path("docs") / options.builddir / "html"
# <==end==>

path オブジェクトの優れた機能の1つはパスを構成するために ‘/’ 演算子を直感的且つ快適に使用できることです。

次にここで紹介したオプションのアクセスです。オプションオブジェクトはタスクで利用できます。それは基本的に属性スタイルのアクセスを提供するディレクトリで(長い options.sphinx.builddir の代わりに options.builddir を入力できる)オプションの変数を検索できます。オプションのプロパティはセクション間でプロパティを共有可能にするためにも便利です。

これにより、分割したファイルとしてシェルスクリプトを取り除きます。

旧い方法の他の欠点を修正する

旧い方法のドキュメントでは、実際にその関数の内部に直接的に docs を追加しました。しかし、その場所でカット&ペーストする必要がありました。Sphinx はドキュメントに外部ファイルを含める方法を提供します。Paver はさらにもっと良い方法で追加します。

ドキュメント作成には複数の問題があります。

  1. 何よりもちゃんと動くことを保証するためにテスト可能なプログラムで、実行可能で、完全なコードになるようにドキュメントとは分割したファイルにコードを書くことは良いことです。
  2. ドキュメントと、合理的且つ理路整然とした内容に沿うドキュメントに含められるサンプルが欲しいです。Python の doctest スタイルはいつも理路整然としたドキュメントに役立つとは限りません。
  3. ドキュメントへインラインで、書かれた内容に関連するコードサンプルを追加することは素晴らしいです。書かれた内容がすぐに理解できる場合、書くことが簡単になります。

#1 と #3 は相互排他的ですが、そうではありません。Paver ではこの問題を解決するために2種類の戦略があります。1つ目の戦略を理解するために index.rst の一部を見てましょう。

# <== include("started/newway/docs/index.rst", "mainpart")==>
Welcome to The New Way's documentation!
=======================================

This is the Paver way of doing things. The key functionality here
is in this powerful piece of code, which I will `include` here in its entirety
so that you can bask in its power::

  # [[[cog include("newway/thecode.py", "code")]]]
  # [[[end]]]

# <==end==>

新しい方法の index.rst では、このスタートガイドで使用されているのと同じ仕組みが見れます。Paver は Ned Batchelder の Cog パッケージを含みます。Cog はあるファイルに Python のスニペットを用意して、それらのスニペットが生成した出力内容をそのファイルの中に組み入れます。テンプレート言語とは違い、Cog はファイルにマーカーを入れて必要になる度に再度生成できるように設計されています。テンプレート言語だと、テンプレートと最終的な出力結果を持っていますが、1つのファイルに両方の内容は持てません。

そのため、私がこのスタートガイドを書いているように、チラッと見て index.rst のコンテンツの適切なインラインを確認できます。そこにある #[[[cog の部分は include() 関数を呼び出していることに気付きます。これは Paver で提供される2つめの戦略です。Paver は Cog で使用する “includedir” を指定させます。これは関連するファイルをそのディレクトリに含めるようにさせます。そして、決定的に追加したい部分のみを簡単に含めることができるように、そういったファイルのセクションを区切らせます。上述したサンプルでは newway/thecode.py ファイルの ‘code’ セクションから取り出します。それでは newway/thecode.py ファイルの中身を覗いてみましょう。

# <== sh("cat docs/samples/started/newway/newway/thecode.py") ==>
"""This is our powerful, code-filled, new-fangled module."""

# [[[section code]]]
def powerful_function_and_new_too():
    """This is powerful stuff, and it's new."""
    return 2*1
# [[[endsection]]]
# <==end==>

Paver は名前セクションを定義するために Cog のような構文があります。そのため、関連するファイ名と追加したいそのセクションで include() 関数を使用してください。セクションはネストして使用することもできます(ドット表記でネストされたセクションを参照します)。

おまけのデプロイサンプル

pavement.py は普通の Python プログラムです。ループやその他の構文はあなたが慣れ親しんだコーディングと同じです。そのオプションは普通の Python プログラムなのでリストやその他のオブジェクトを含めることができます。複数のホストへデプロイする必要があるときは?単にそのオプションにホストを追加してループさせるだけで良いです。

複数のサーバへ新しい方法プロジェクトの HTML ファイルをデプロイしたいと仮定してください。私は1つのサーバしか持っていないけれど、これは私が Paver そのものに行うこととよく似ています。先ず、deploy タスクに使用する複数の変数を設定します。

# <== include('started/newway/pavement.py', 'deployoptions')==>
options(
    deploy = Bunch(
        htmldir = path('newway/docs'),
        hosts = ['host1.hostymost.com', 'host2.hostymost.com'],
        hostpath = 'sites/newway'
    )
)
# <==end==>

ご覧の通り、オプションの中にどのようなオブジェクトでも望みのモノを追加できます。今は deploy タスクそのもののためです。

# <== include("started/newway/pavement.py", "deploy")==>
@task
@cmdopts([
    ('username=', 'u', 'Username to use when logging in to the servers')
])
def deploy(options):
    """Deploy the HTML to the server."""
    for host in options.hosts:
        sh("rsync -avz -e ssh %s/ %s@%s:%s/" % (options.htmldir,
            options.username, host, options.hostpath))
# <==end==>

新たな “cmdopts” デコレータがあります。pavement に記述したくないパスワードのような機密情報があると仮定してください。cmdopts を使用するそのタスクのコマンドラインオプションを簡単に作ることができます。options.deploy.username はコマンドラインでユーザが入力した内容をセットします。

オプションオブジェクト内を探す必要もありません。Paver はそのタスクとして同じ名前でセクションにあるオプションを優先します。そのため、options.username は他のセクションで username が存在したとしても options.deploy.username を優先して選択します。

deploy タスクは各ホストに rsync コマンドを実行するために単純なループを使用します。rsync コマンドがどうなるかを確認するためにユーザ名を入力して dry run を実行してみましょう。

# <== sh("cd docs/samples/started/newway; paver -n deploy -u kevin")==>
---> pavement.deploy
rsync -avz -e ssh newway/docs/ kevin@host1.hostymost.com:sites/newway/
rsync -avz -e ssh newway/docs/ kevin@host2.hostymost.com:sites/newway/
# <==end==>

この次にどこへ行くのか

最初に行うことは Paver を使い始めることです。これまで説明したように、あなたのワークフローに Paver を組み込むことは既存プロジェクトであっても簡単です。

paver help コマンドを使用してください。

あなたが今すぐ詳細を知りたいなら pavement ファイルPaver 標準ライブラリ を読んでみると良いでしょう。