タスクの定義¶
Fabric 1.1からは、fabfileの中でどのオブジェクトをタスクとして示すかを定義するために利用できる、2つのはっきりと異なった方法があります:
- 1.1からスタートした "新しい" 方法は
Task
もしくはそのサブクラスのインスタンスを考慮し、また、ネストされた名前空間を構築できるようにするためにインポートされたモジュールに落とし込まれています。 - "クラシックな" 方法は1.0以前からのもので、すべてのパブリックな呼び出し可能なオブジェクト(関数やクラスなど)を考慮し、インポートされたモジュールへの再帰的には処理せず、そのfabfileのオブジェクトのみ考慮します。
注釈
これらの2つの方法は 相互に排他的です : もしFabricがfabfile内やインポートしたモジュール内に どんな 新しいスタイルのタスクオブジェクトでも見つければ、タスク宣言のこのメッソドをコミットしたとしてみなされ、Task
以外の呼び出しは考慮されません。新しいスタイルのタスクが なければ クラシックな挙動に戻ります。
このドキュメントではこの2つのメソッドを詳細に説明します。
注釈
fabfileでどのタスクが fab
経由で実行されうるかを厳密に確認するには fab --list
を使います。
新しいスタイルのタスク¶
Fabric 1.1では新しい機能を促進し、プログラミングのベストプラクティスを可能にするために Task
クラスを導入しました。特に:
- オブジェクト指向のタスク。継承とそれに伴うすべては、単純に関数オブジェクトを渡し回るより、より実用的なコードの最利用を可能のします。タスク宣言のクラシックなスタイルは完全に排除されているわけではありませんでしたが、ことをとても簡単にするわけでもありませんでした。
- 名前空間。タスクの宣言を明確なメソッドにすることによって、例えば、(クラシックな手順のもとで有効な "タスク" として表示される)Python
os
モジュールのコンテンツでタスクリストを汚すことなく、再帰的な名前空間のセットアップが容易になります。
Task
の前置きとして、新しいタスクをセットアップするには2つの方法があります:
@task
で通常のモジュールレベルの関数をデコレートします。これはTask
サブクラス内の関数を透過的にラップします。実行時には関数名がタスク名として使われます。Task
サブクラス(Task
自身は抽象的であることを意図されています)はrun
メソッドを定義し、モジュールレベルであなたのサブクラスをインスタンス化します。インスタンスのname
属性がタスク名として使われます。省略した場合には、その代わりにインスタンスの変数名が使われます。
新しいスタイルのタスクの利用はまた、 名前空間 のセットアップも可能にします。
@task
デコレータ¶
新しいスタイルのタスク機能を利用するもっとも簡単な方法は基本のタスク関数を @task
: で囲ってしまう方法です:
from fabric.api import task, run
@task
def mytask():
run("a command")
このデコレータが使われると、このデコレータで囲われた関数 のみ が有効なタスクとして読み込まれることをFabricに伝えます。(表示されない場合は、 クラシックスタイルの 挙動で動作しています)
引数¶
@task
はまた、引数とともに呼び出してその挙動をカスタマイズすることもできます。以下に記述されていない引数は利用中の task_class
のコンストラクタにその最初の関数自身を最初の引数として渡されます。(詳細は @task とのカスタムサブクラスの使用 をご覧ください)
task_class
:Task
のサブクラスで、デコレートされた関数をラップするために使用されます。デフォルトはWrappedCallableTask
です。aliases
: ラップされた関数用のエイリアスとして繰り返し使える文字列名。詳細は エイリアス をご覧ください。alias
:aliases
と似ていますが、繰り返しできない単一の文字列引数を取ります。もしalias
とaliases
の両方が設定されている場合、aliases
の方が優先されます。default
: デコレートされたタスクが、タスク名としてそれが含むモジュールの代役を務めるかどうかを決めるための真偽値。デフォルトのタスク を参照してください。name
: そのタスクがコマンドラインインターフェースに表示されるときの名称を設定する文字列です。Pythonのビルトインを別の方法でシャドーイングするタスク名で便利です(これは技術的には可能ですが、好まれませんし、バグの温床にもなります)。
エイリアス¶
以下は、人間が読める長めのタスク名とすばやくタイプするための短めのタスク名の両方の利用を手助けするための alias
キーワード引数の簡単な利用例です:
from fabric.api import task
@task(alias='dwm')
def deploy_with_migrations():
pass
このfabfileで --list
を呼び出すとオリジナルの``deploy_with_migrations`` とエイリアスの dwm
: を表示します:
$ fab --list
Available commands:
deploy_with_migrations
dwm
同じ関数に複数のエイリアスが必要な場合は、単に aliases
キーワード引数をスワップします。これにより、単一の文字列の代わりに繰り返し利用可能な文字列が取られます。
デフォルトのタスク¶
aliases と同じような方法で、モジュール内の与えられたタスクを "default" タスクとして指定するときに便利な場合があり、そのモジュール名を 単に 言及することで呼び出すこともできます。これによりタイピングが省略できたり、一つの "メイン" タスクとたくさんの関連タスクもしくはサブモジュールがある場合のより整理された構成が可能になります。
例えば、 deploy
サブモジュールが新しいサーバのプロビジョニング、コードのプッシュ、データベースの移行などのタスクを含んでいるとして、デフォルトの "just deploy" アクションとしてタスクを強調できるととても便利でしょう。そうした deploy.py
モジュールは次のようになります:
from fabric.api import task
@task
def migrate():
pass
@task
def push():
pass
@task
def provision():
pass
@task
def full_deploy():
if not provisioned:
provision()
push()
migrate()
タスクリストは以下のようになります (単に deploy
を読み込んでいるだけの簡単なトップレベルの fabfile.py
であると仮定します):
$ fab --list
Available commands:
deploy.full_deploy
deploy.migrate
deploy.provision
deploy.push
デプロイのたびに deploy.full_deploy
を呼び出すのはちょっと古めかしいですし、チームに加わった新らしい方にとってはこれが実行する正しいタスクなのか迷うことでしょう。
@task
への default
キーワード引数を利用することにより、例えば、デフォルトのタスクとして full_deploy
をタグ付けすることができます:
@task(default=True)
def full_deploy():
pass
このようにアップデートするとタスクリストは以下のようになります:
$ fab --list
Available commands:
deploy
deploy.full_deploy
deploy.migrate
deploy.provision
deploy.push
full_deploy
は明示的なタスクとしてそのままあることに留意してください。そして、 full_deploy
のある種トップレベルのエイリアスとして deploy
が表示されます。
もし一つのモジュール内に複数の default=True
がセットされている場合は、最後に読み込まれたもの(通常はファイルの最も下にあるもの)が優先されます。
トップレベルのデフォルトタスク¶
トップレベルのfabfileで @task(default=True)
を使用すると、ユーザーがタスク名なしで fab
を呼び出した際にそのタスクを実行します(例えば、 make
に似ています)。このショートカットの使用時にはタスク自身に引数を指定することはできません。引数が必要な場合は通常のタスク呼び出しを実行してください。
Task
サブクラス¶
クラシックスタイルのタスク に慣れている場合は、 Task
はその run
メソッドがクラシックなタスクとそのまま同等であると考えると分かりやすいでしょう。その引数が(self
以外の)タスクの引数で、そのボディが実行される内容となります。
例えば、この新しいスタイルのタスクは:
class MyTask(Task):
name = "deploy"
def run(self, environment, domain="whatever.com"):
run("git clone foo")
sudo("service apache2 restart")
instance = MyTask()
この関数ベースのタスクとまったく同等です:
@task
def deploy(environment, domain="whatever.com"):
run("git clone foo")
sudo("service apache2 restart")
クラスのインスタンスをどのように作成しているかに留意してくだだい。これは実際に動作する単純な通常のPythonオブジェクト指向プログラミングです。この時点では小さなひな形に過ぎませんが、例えば、Fabricはインスタンス作成時に与える名前は気にせず、そのインスタンスの 名前
属性のみを気にします。クラスの力(ちから)を利用可能にすることの恩恵は検討に値するでしょう。
将来的にはこのAPIを拡張して、このエクスペリエンスをより洗練させていく予定です。
@task
とのカスタムサブクラスの使用¶
カスタムな Task
サブクラスと @task
を結合させることも可能です。これはコアの実行ロジックがクラス/オブジェクト指向ではないけれどもクラスのメタプログラミングやそれと似たテクニックを活用したい場合に有用です。
特に、呼び出し可能なものとして最初のコンストラクタ引数を取るように設計されているすべての Task
サブクラスは(ビルトインの WrappedCallableTask
と同様に)、 @task
への task_class
引数として指定することができます。
Fabricは与えられたクラスのコピーを自動的にインスタンス化し、最初の引数としてラップされた関数に渡されます。デコレーターに渡されるすべての他の引数/キーワード引数( 引数 に記述されている "特別な" 引数以外)はその後に追加されます。
これを明確にするための簡単でいくらか工夫されている例をお見せします:
from fabric.api import task
from fabric.tasks import Task
class CustomTask(Task):
def __init__(self, func, myarg, *args, **kwargs):
super(CustomTask, self).__init__(*args, **kwargs)
self.func = func
self.myarg = myarg
def run(self, *args, **kwargs):
return self.func(*args, **kwargs)
@task(task_class=CustomTask, myarg='value', alias='at')
def actual_task():
pass
このfabfileが読み込まれた時、CustomTask
のコピーがインスタンス化され、事実上は以下を呼び出しています:
task_obj = CustomTask(actual_task, myarg='value')
alias
キーワード引数がデコレーター自身によってどのように取り除かれるか、そしてクラスのインスタンス化には到達しないことに留意してください。これは コマンドラインタスクの引数 の動作と機能的に同一です。
名前空間¶
クラシックなタスク では、複数のfabfileは単一でフラットなタスク名のセットに制限され、それらを体系化する本格的な方法はありません。Fabric 1.1以降では、タスクを新しいやり方(@task
もしくは ご自分の Task
サブクラスのインスタンス経由)で宣言すれば 名前空間 を活用することができます:
- fabfileにインポートされたどのモジュールオブジェクトも再帰的に処理され、追加のタスクオブジェクトを探します。
- サブモジュール内では、Python標準の
__all__
モジュールレベル変数名を使ってどのオブジェクトが "exported" されるかをコントロールすることができます(有効な新しいタスクオブジェクトでなければなりませんが)。 - これらのタスクは、Python自身のインポートシンタックスと似た、それを含んだモジュールをベースにした新しいドットノーテーション名が与えられます。
単純な複雑なものまでfabfileのパッケージを組み立てて、どのように動作するか見てみましょう。
基本¶
まずはいくつかのタスクを含む一つの __init__.py
(簡潔性のためFabricのAPIは省略します)から始めてみましょう:
@task
def deploy():
...
@task
def compress():
...
fab --list
の出力は次のようになるでしょう:
deploy
compress
ここでは名前空間は一つだけで、 "root" もしくはグローバルな名前空間です。今は単純に見えますが、実世界のfabfileではたくさんのタスクがあり、管理が難しくなり得ます。
サブモジュールのインポート¶
前述のとおり、モジュールがPythonのインポートパス上のどこにあるかには関係なく、Fabricはインポートされたモジュールオブジェクトのタスクを調べます。今のところは、とりあえず "手近にある" 自前のタスクを含めたいと思いますので、そうですね、ロードバランサを扱うためのパッケージに新しいサブモジュール lb.py
を作ってみましょう:
@task
def add_backend():
...
そして __init__.py
の一番上にこれを追加します:
import lb
さて、これで fab --list
は次のようになります:
deploy
compress
lb.add_backend
モジュールに一つだけのタスクではある種他愛のないもののように見えますが、その恩恵はかなり明白だと思います。
さらに奥深くへ¶
名前空間化は単にひとつのレベルに制限されることはありません。より大きなセットアップを持ち、データベース関連のタスクのための名前空間が必要になっていて、その中に追加の別のタスクがあるとしましょう。 db/
と名前を付けられたサブパッケージを作り、その中に migrations.py
モジュールを起きます:
@task
def list():
...
@task
def run():
...
このモジュールは db
をインポートしているすべてから見えるようにする必要があるので、このサブパッケージの __init__.py
に以下を追加します:
import migrations
最後のステップとして、ルートレベルの __init__.py
にサブパッケージをインポートします。これで最初の何行かは以下のようになります:
import lb
import db
そして、ファイルツリーは以下のようになります:
.
├── __init__.py
├── db
│ ├── __init__.py
│ └── migrations.py
└── lb.py
fab --list
は次のようになります:
deploy
compress
lb.add_backend
db.migrations.list
db.migrations.run
また、タスクを db/__init__.py
内に直接設定(もしくはインポート)することも可能で、ご想像どおり db.<なんちゃら>
として表示されます。
__all__
での制限¶
インポートされたモジュールをFabricが分析するときに、モジュールレベル __all__
変数(変数名のリスト)のPythonの決まり事を利用することによって、Fabricが "見る" ものを制限することができます。もし何かの理由によりデフォルトでは db.migrations.run
タスクを表示させたくない場合、 db/migrations.py
の一番上に以下を追加することができます:
__all__ = ['list']
ここには 'run'
がないことに留意してください。もし必要なら run
をこの階層のどこかに直接インポートすることも可能ですが、そうでなければ、隠れれたままになります。
ひとつ上へ¶
これまで、fabfileパッケージを片付いた状態に維持し、直接的な方法でインポートしてきましたが、ファイルシステムのレイアウトはここでは実際には考慮していません。Fabricのすべてのローダーが気にするのは、インポートされた時のモジュールに与えられた名称です。
例えば、ルートの __init__.py
の市場うえを次のように変更すると:
import db as database
タスクリストは次のように変わります:
deploy
compress
lb.add_backend
database.migrations.list
database.migrations.run
これは他のどのインポートにも適用されます。サードパーティのモジュールを自分のタスク階層にインポートしたり、深くネストされたモジュールを取ってきてトップレベル近くに置くことも可能です。
ネストされたリスト出力¶
最後に、このセクションではデフォルトのFabric --list
出力を使用してきました。これにより実際のタスク名は何なのかがより明確になります。とは言え、--list-format
オプションに nested
を渡すと、よりネストされていたりツリーライクな表示を得られます:
$ fab --list-format=nested --list
Available commands (remember to call as module.[...].task):
deploy
compress
lb:
add_backend
database:
migrations:
list
run
"実際の" タスク名が少し分かりにくくなりますが、この表示は大規模な名前空間でのタスク構成を把握する簡単な方法を提供します。
クラシックなタスク¶
新しいスタイルの Task
ベースのタスクが見つからない場合、Fabricはfabfile内の呼び出し可能などんなオブジェクトでも検討します。 ただし、 次を除きます:
- アンダースコア(
_
)で始まる名称の呼び出し可能なオブジェクト。言い換えると、ここではPythonの通常の "プライベート" 規則が適用されます。 - Fabric自身内で定義されている呼び出し可能なオブジェクト。
run
とsudo
などのFabric自身の関数はタスクリストには表示されません。
インポート¶
Pythonの import
ステートメントは事実上、あなたのモジュール名前空間にあるインポートされたオブジェクトを含みます。Fabricのfabfileは単なるPythonモジュールなので、インポートもまた、fabfile自身に定義されているすべてと一緒に出来る限りクラシックスタイルのタスクとみなされます。
注釈
これはインポートされた 呼び出し可能なオブジェクト のみに適用され、モジュールには適用されません。インポートされたモジュールは 新しいスタイルのタスク を含む場合のみ実行され、その時点でこのセクションは適用されません。
このため、私達としては後ろに module.callable()
が続くインポートの import module
形式を利用するよう強くおすすめします。これにより、結果として from module import callable
を行うよりもよりきれいなfabfile APIになります。
ウェブサービスから何らかのデータを取り出すために urllib.urlopen
を使用しているサンプルのfabfileを例に上げましょう:
from urllib import urlopen
from fabric.api import run
def webservice_read():
objects = urlopen('http://my/web/service/?foo=bar').read().split()
print(objects)
これはかなり単純でエラー無しで実行できます。しかし、このfabfileで fab --list
を実行するとどうなるか見てみましょう:
$ fab --list
Available commands:
webservice_read List some directories.
urlopen urlopen(url [, data]) -> open file-like object
1つのタスクしかないfabfileで2つの "タスク" が表示されています。これはよくありませんし、疑うことを知らないユーザーがうっかりと fab urlopen
を呼びだそうとするかもしれませんし、たぶんまともには動作しないでしょう。実世界のfabfileを想像してみてください。かなり複雑になることが多いでしょうし、すぐに乱雑になってしまうことがわかってもらえると思います。
参考のため、以下がおすすめの方法になります:
import urllib
from fabric.api import run
def webservice_read():
objects = urllib.urlopen('http://my/web/service/?foo=bar').read().split()
print(objects)
簡単な変更ですが、このfabfileを使う方は誰でもこれにより少しは幸せになるでしょう。