強大的 Python 任務自動化工具!invoke 十分鐘入門指南
接著前面的《tox 教程》,以及剛翻譯好的《nox文件》,我們繼續聊聊 Python 任務自動化的話題。
nox 的作者在去年的 Pycon US 上,做了一場題為《Break the Cycle: Three excellent Python tools to automate repetitive tasks》的分享(B站觀看地址:https://b23.tv/av86640235),她介紹了三個任務自動化工具:tox、nox 和 invoke,本文的話題正好就是最後的 invoke。
1、invoke 可以做什麼?
invoke 是從著名的遠端部署工具 Fabric 中分離出來的,它與 paramiko 一起是 Fabric 的兩大最核心的基礎元件。
除了作為命令列工具,它專注於“任務執行”(task execution),可以標註和組織任務,並通過 CLI(command-line interface,即命令列介面) 和 shell 命令來執行任務。
同樣是任務自動化工具,invoke 與我們之前介紹過的 tox/nox 在側重點上有所不同:
- tox/nox 主要是在打包、測試、持續整合等方面的自動化(當然它們能做的還不止於此)
- invoke 則更具普遍性,可以用在任何需要“執行任務”的場景,可以是無相關性的任務組,也可以是有順序依賴的分步驟的工作流
invoke 在 Github 上有 2.7K star,十分受歡迎,接下來我們看看它如何使用?
2、怎麼使用 invoke?
首先,安裝很簡單:pip install invoke
。
其次,簡單使用時有以下要素:
- 任務檔案。建立一個 tasks.py 檔案。
- @task 裝飾器。在一個函式上新增 @task 裝飾器,即可將該函式標記為一個任務,接受 invoke 的排程管理。
- 上下文引數。給被裝飾的函式新增一個上下文引數(context argument),注意它必須作為第一個引數,而命名按約定可以是
c
或ctx
或context
。 - 命令列執行。在命令列中執行
invoke --list
來檢視所有任務,執行invoke xxx
來執行名為 xxx 的任務。命令列中的“invoke”可以簡寫成“inv”。
以下是一個簡單的示例:
# 檔名:tasks.py
from invoke import task
@task
def hello(c):
print("Hello world!")
@task
def greet(c, name):
c.run(f"echo {name}加油!")
在上述程式碼中,我們定義了兩個任務:
- ”hello“任務呼叫了 Python 內建的 print 函式,會列印一個字串“Hello world!”
- “greet”任務呼叫了上下文引數的 run() 方法,可以執行 shell 命令,同時本例中還可以接收一個引數。在 shell 命令中,echo 可理解成列印,所以這也是一個列印任務,會打印出“xxx加油!”(xxx 是我們傳的引數)
以上程式碼寫在 tasks.py 檔案中,首先匯入裝飾器 from invoke import task
,@task 裝飾器可以不帶引數,也可以帶引數(參見下一節),被它裝飾了的函式就是一個任務。
上下文引數(即上例的“c”)必須要顯式地指明,如果缺少這個引數,執行時會丟擲異常:“TypeError: Tasks must have an initial Context argument!”
然後在 tasks.py 檔案的同級目錄中,開啟命令列視窗,執行命令。如果執行的位置找不到這個任務檔案,則會報錯:“Can't find any collection named 'tasks'!”
正常情況下,通過執行inv --list
或者inv -l
,可以看到所有任務的列表(按字母表順序排序):
>>> inv -l
Available tasks:
greet
hello
我們依次執行這兩個任務,其中傳參時可以預設按位置引數傳參,也可以指定關鍵字傳參。結果是:
>>> inv hello
Hello world!
>>> inv greet 武漢
武漢加油!
>>> inv greet --name="武漢"
武漢加油!
缺少傳參時,報錯:'greet' did not receive required positional arguments: 'name';多餘傳參時,報錯:No idea what '???' is!
3、 如何用好 invoke?
介紹完 invoke 的簡單用法,我們知道了它所需的幾項要素,也大致知道了它的使用步驟,接下來是它的其它用法。
3.1 新增幫助資訊
在上例中,“inv -l”只能看到任務名稱,缺少必要的輔助資訊,為了加強可讀性,我們可以這樣寫:
@task(help={'name': 'A param for test'})
def greet(c, name):
"""
A test for shell command.
Second line.
"""
c.run(f"echo {name}加油!")
其中,文件字串的第一行內容會作為摘錄,在“inv -l”的查詢結果中展示,而且完整的內容與 @task 的 help 內容,會對應在“inv --help”中展示:
>>> inv -l
Available tasks:
greet A test for shell command.
>>> inv --help greet
Usage: inv[oke] [--core-opts] greet [--options] [other tasks here ...]
Docstring:
A test for shell command.
Second line.
Options:
-n STRING, --name=STRING A param for test
3.2 任務的分解與組合
通常一個大任務可以被分解成一組小任務,反過來,一系列的小任務也可能被串連成一個大任務。在對任務作分解、抽象與組合時,這裡有兩種思路:
- 對內分解,對外統一:只定義一個 @task 的任務,作為總體的任務入口,實際的處理邏輯可以抽象成多個方法,但是外部不感知到它們
- 多點呈現,單點彙總:定義多個 @task 的任務,外部可以感知並分別呼叫它們,同時將有關聯的任務組合起來,呼叫某個任務時,也執行其它相關聯的任務
第一種思路很容易理解,實現與使用都很簡單,但是其缺點是缺少靈活性,難於單獨執行其中的某個/些子任務。適用於相對獨立的單個任務,通常也不需要 invoke 就能做到(使用 invoke 的好處是,擁有命令列的支援)。
第二種思路更加靈活,既方便單一任務的執行,也方便多工的組合執行。實際上,這種場景才是 invoke 發揮最大價值的場景。
那麼,invoke 如何實現分步任務的組合呢?可以在 @task 裝飾器的“pre”與“post”引數中指定,分別表示前置任務與後置任務:
@task
def clean(c):
c.run("echo clean")
@task
def message(c):
c.run("echo message")
@task(pre=[clean], post=[message])
def build(c):
c.run("echo build")
clean 與 message 任務作為子任務,可以單獨呼叫,也可以作為 build 任務的前置與後置任務而組合使用:
>>> inv clean
clean
>>> inv message
message
>>> inv build
clean
build
message
這兩個引數是列表型別,即可設定多個任務。另外,在預設情況下,@task 裝飾器的位置引數會被視為前置任務,接著上述程式碼,我們寫一個:
@task(clean, message)
def test(c):
c.run("echo test")
然後執行,會發現兩個引數都被視為了前置任務:
>>> inv test
clean
message
test
3.3 模組的拆分與整合
如果要管理很多相對獨立的大型任務,或者需要多個團隊分別維護各自的任務,那麼,就有必要對 tasks.py 作拆分與整合。
例如,現在有多份 tasks.py,彼此是相對完整而獨立的任務模組,不方便把所有內容都放在一個檔案中,那麼,如何有效地把它們整合起來管理呢?
invoke 提供了這方面的支援。首先,只能保留一份名為“tasks.py”的檔案,其次,在該檔案中匯入其它改名後的任務檔案,最後,使用 invoke 的 Collection 類把它們關聯起來。
我們把本文中第一個示例檔案改名為 task1.py,並新建一個 tasks.py 檔案,內容如下:
# 檔名:tasks.py
from invoke import Collection, task
import task1
@task
def deploy(c):
c.run("echo deploy")
namespace = Collection(task1, deploy)
每個 py 檔案擁有獨立的名稱空間,而在此處,我們用 Collection 可以創建出一個新的名稱空間,從而實現對所有任務的統一管理。效果如下:
>>> inv -l
Available tasks:
deploy
task1.greet
task1.hello
>>> inv deploy
deploy
>>> inv task1.hello
Hello world!
>>> inv task1.greet 武漢
武漢加油!
關於不同任務模組的匯入、巢狀、混合、起別名等內容,還有不少細節,請查閱官方文件瞭解。
3.4 互動式操作
某些任務可能需要互動式的輸入,例如要求輸入“y”,按回車鍵後才會繼續執行。如果在任務執行期間需要人工參與,那自動化任務的能力將大打折扣。
invoke 提供了在程式執行期的監控能力,可以監聽stdout
和stderr
,並支援在stdin
中輸入必要的資訊。
例如,假設某個任務(excitable-program)在執行時會提示“Are you ready? [y/n]”,只有輸入了“y”並按下回車鍵,才會執行後續的操作。
那麼,在程式碼中指定 responses 引數的內容,只要監聽到匹配資訊,程式會自動執行相應的操作:
responses = {r"Are you ready? \[y/n\] ": "y\n"}
ctx.run("excitable-program", responses=responses)
responses 是字典型別,鍵值對分別為監聽內容及其迴應內容。需注意,鍵值會被視為正則表示式,所以像本例中的方括號就要先轉義。
3.5 作為命令列工具庫
Python 中有不少好用的命令列工具庫,比如標準庫中的argparse
、Flask 作者開源的click
與谷歌開源的fire
等等,而 invoke 也可以作為命令列工具庫使用。
(PS:有位 Prodesire 同學寫了“Python 命令列之旅”的系列文章,詳細介紹了其它幾個命令列工具庫的用法,我在公眾號“Python貓”裡轉載過大部分,感興趣的同學可檢視歷史文章。)
事實上,Fabric 專案最初把 invoke 分離成獨立的庫,就是想讓它承擔解析命令列與執行子命令的任務。所以,除了作為自動化任務管理工具,invoke 也可以被用於開發命令列工具。
官方文件中給出了一個示例,我們可以瞭解到它的基本用法。
假設我們要開發一個 tester 工具,讓使用者pip install tester
安裝,而此工具提供兩個執行命令:tester unit
和tester intergration
。
這兩個子命令需要在 tasks.py 檔案中定義:
# tasks.py
from invoke import task
@task
def unit(c):
print("Running unit tests!")
@task
def integration(c):
print("Running integration tests!")
然後在程式入口檔案中引入它:
# main.py
from invoke import Collection, Program
from tester import tasks
program = Program(namespace=Collection.from_module(tasks), version='0.1.0')
最後在打包檔案中宣告入口函式:
# setup.py
setup(
name='tester',
version='0.1.0',
packages=['tester'],
install_requires=['invoke'],
entry_points={
'console_scripts': ['tester = tester.main:program.run']
}
)
如此打包發行的庫,就是一個功能齊全的命令列工具了:
$ tester --version
Tester 0.1.0
$ tester --help
Usage: tester [--core-opts] <subcommand> [--subcommand-opts] ...
Core options:
... core options here, minus task-related ones ...
Subcommands:
unit
integration
$ tester --list
No idea what '--list' is!
$ tester unit
Running unit tests!
上手容易,開箱即用,invoke 不失為一款可以考慮的命令列工具庫。更多詳細用法,請查閱文件 。
4、小結
invoke 作為從 Fabric 專案中分離出來的獨立專案,它自身具備一些完整而強大的功能,除了可用於開發命令列工具,它還是著名的任務自動化工具。
本文介紹了它的基礎用法與 5 個方面的中級內容,相信讀者們會對它產生一定的瞭解。invoke 的官方文件十分詳盡,限於篇幅,本文不再詳細展開,若感興趣,請自行查閱文件哦。
--------------
公眾號:Python貓(ID: python_cat)
頭條號:Python貓
知乎:豌豆花下貓
掘金:豌豆花下貓
公眾號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦