Linux中國

將你的 Python 腳本轉換為命令行程序

在我的職業生涯中,我寫過、用過和看到過很多隨意的腳本。一些人需要半自動化完成任務,於是它們誕生了。一段時間後,它們變得越來越大。它們在一生中可能轉手很多次。我常常希望這些腳本提供更多的命令行工具式的感覺。但是,從一次性腳本到合適的工具,真正提高質量水平有多難呢?事實證明這在 Python 中並不難。

搭建骨架腳本

在本文中,我將從一小段 Python 代碼開始。我將把它應用到 scaffold 模塊中,並使用 click 庫擴展它以接受命令行參數。

#!/usr/bin/python

from glob import glob
from os.path import join, basename
from shutil import move
from datetime import datetime
from os import link, unlink

LATEST = 'latest.txt'
ARCHIVE = '/Users/mark/archive'
INCOMING = '/Users/mark/incoming'
TPATTERN = '%Y-%m-%d'

def transmogrify_filename(fname):
    bname = basename(fname)
    ts = datetime.now().strftime(TPATTERN)
    return '-'.join([ts, bname])

def set_current_latest(file):
    latest = join(ARCHIVE, LATEST)
    try:
        unlink(latest)
    except:
        pass
    link(file, latest)

def rotate_file(source):
    target = join(ARCHIVE, transmogrify_filename(source))
    move(source, target)
    set_current_latest(target)

def rotoscope():
    file_no = 0
    folder = join(INCOMING, '*.txt')
    print(f'Looking in {INCOMING}')
    for file in glob(folder):
        rotate_file(file)
        print(f'Rotated: {file}')
        file_no = file_no + 1
    print(f'Total files rotated: {file_no}')

if __name__ == '__main__':
    print('This is rotoscope 0.4.1. Bleep, bloop.')
    rotoscope()

本文所有沒有在這裡插入顯示的代碼示例,你都可以在 https://codeberg.org/ofosos/rotoscope 中找到特定版本的代碼。該倉庫中的每個提交都描述了本文操作過程中一些有意義的步驟。

這個片段做了幾件事:

  • 檢查 INCOMING 指定的路徑中是否有文本文件
  • 如果存在,則使用當前時間戳創建一個新文件名,並將其移動到 ARCHIVE
  • 刪除當前的 ARCHIVE/latest.txt 鏈接,並創建一個指向剛剛添加文件的新鏈接

作為一個示例,它很簡單,但它會讓你理解這個過程。

使用 Pyscaffold 創建應用程序

首先,你需要安裝 scaffoldclicktox Python 庫

$ python3 -m pip install scaffold click tox

安裝 scaffold 後,切換到示例的 rotoscope 項目所在的目錄,然後執行以下命令:

$ putup rotoscope -p rotoscope 
    --force --no-skeleton -n rotoscope 
    -d 'Move some files around.' -l GLWT 
    -u http://codeberg.org/ofosos/rotoscope 
    --save-config --pre-commit --markdown

Pyscaffold 會重寫我的 README.md,所以從 Git 恢復它:

$ git checkout README.md

Pyscaffold 在文檔中說明了如何設置一個完整的示例項目,我不會在這裡介紹,你之後可以探索。除此之外,Pyscaffold 還可以在項目中為你提供持續集成(CI)模板:

  • 打包: 你的項目現在啟用了 PyPi,所以你可以將其上傳到一個倉庫並從那裡安裝它。
  • 文檔: 你的項目現在有了一個完整的文檔文件夾層次結構,它基於 Sphinx,包括一個 readthedocs.org 構建器。
  • 測試: 你的項目現在可以與 tox 一起使用,測試文件夾包含運行基於 pytest 的測試所需的所有樣板文件。
  • 依賴管理: 打包和測試基礎結構都需要一種管理依賴關係的方法。setup.cfg 文件解決了這個問題,它包含所有依賴項。
  • 預提交鉤子: 包括 Python 源代碼格式工具 black 和 Python 風格檢查器 flake8。

查看測試文件夾並在項目目錄中運行 tox 命令,它會立即輸出一個錯誤:打包基礎設施無法找到相關庫。

現在創建一個 Git 標記(例如 v0.2),此工具會將其識別為可安裝版本。在提交更改之前,瀏覽一下自動生成的 setup.cfg 並根據需要編輯它。對於此示例,你可以修改 LICENSE 和項目描述,將這些更改添加到 Git 的暫存區,我必須禁用預提交鉤子,然後提交它們。否則,我會遇到錯誤,因為 Python 風格檢查器 flake8 會抱怨糟糕的格式。

$ PRE_COMMIT_ALLOW_NO_CONFIG=1 git commit

如果這個腳本有一個入口點,用戶可以從命令行調用,那就更好了。現在,你只能通過找 .py 文件並手動執行它來運行。幸運的是,Python 的打包基礎設施有一個很好的「罐裝」方式,可以輕鬆地進行配置更改。將以下內容添加到 setup.cfgoptions.entry_points 部分:

console_scripts =
    roto = rotoscope.rotoscope:rotoscope

這個更改會創建一個名為 roto 的 shell 命令,你可以使用它來調用 rotoscope 腳本,使用 pip 安裝 rotoscope 後,可以使用 roto 命令。

就是這樣,你可以從 Pyscaffold 免費獲得所有打包、測試和文檔設置。你還獲得了一個預提交鉤子來保證(大部分情況下)你按照設定規則提交。

CLI 工具化

現在,一些值會硬編碼到腳本中,它們作為命令 參數 會更方便。例如,將 INCOMING 常量作為命令行參數會更好。

首先,導入 click 庫,使用 Click 提供的命令裝飾器對 rotoscope() 方法進行裝飾,並添加一個 Click 傳遞給 rotoscope 函數的參數。Click 提供了一組驗證器,因此要向參數添加一個路徑驗證器。Click 還方便地使用函數的內嵌字元串作為命令行文檔的一部分。所以你最終會得到以下方法簽名:

@click.command()
@click.argument('incoming', type=click.Path(exists=True))
def rotoscope(incoming):
    """
    Rotoscope 0.4 - Bleep, blooop.
    Simple sample that move files.
    """

主函數會調用 rotoscope(),它現在是一個 Click 命令,不需要傳遞任何參數。

選項也可以使用 環境變數 自動填充。例如,將 ARCHIVE 常量改為一個選項:

@click.option('archive', '--archive', default='/Users/mark/archive', envvar='ROTO_ARCHIVE', type=click.Path())

使用相同的路徑驗證器。這一次,讓 Click 填充環境變數,如果環境變數沒有提供任何內容,則默認為舊常量的值。

Click 可以做更多的事情,它有彩色的控制台輸出、提示和子命令,可以讓你構建複雜的 CLI 工具。瀏覽 Click 文檔會發現它的更多功能。

現在添加一些測試。

測試

Click 對使用 CLI 運行器 運行端到端測試 提供了一些建議。你可以用它來實現一個完整的測試(在 示例項目 中,測試在 tests 文件夾中。)

測試位於測試類的一個方法中。大多數約定與我在其他 Python 項目中使用的非常接近,但有一些細節,因為 rotoscope 使用 click。在 test 方法中,我創建了一個 CliRunner。測試使用它在一個隔離的文件系統中運行此命令。然後測試在隔離的文件系統中創建 incomingarchive 目錄和一個虛擬的 incoming/test.txt 文件,然後它調用 CliRunner,就像你調用命令行應用程序一樣。運行完成後,測試會檢查隔離的文件系統,並驗證 incoming 為空,並且 archive 包含兩個文件(最新鏈接和存檔文件)。

from os import listdir, mkdir
from click.testing import CliRunner
from rotoscope.rotoscope import rotoscope

class TestRotoscope:
    def test_roto_good(self, tmp_path):
        runner = CliRunner()

        with runner.isolated_filesystem(temp_dir=tmp_path) as td:
            mkdir("incoming")
            mkdir("archive")
            with open("incoming/test.txt", "w") as f:
                f.write("hello")

            result = runner.invoke(rotoscope, ["incoming", "--archive", "archive"])
            assert result.exit_code == 0

            print(td)
            incoming_f = listdir("incoming")
            archive_f = listdir("archive")
            assert len(incoming_f) == 0
            assert len(archive_f) == 2

要在控制台上執行這些測試,在項目的根目錄中運行 tox

在執行測試期間,我在代碼中發現了一個錯誤。當我進行 Click 轉換時,rotoscope 只是取消了最新文件的鏈接,無論它是否存在。測試從一個新的文件系統(不是我的主文件夾)開始,很快就失敗了。我可以通過在一個很好的隔離和自動化測試環境中運行來防止這種錯誤。這將避免很多「它在我的機器上正常工作」的問題。

搭建骨架腳本和模塊

本文到此結束,我們可以使用 scaffoldclick 完成一些高級操作。有很多方法可以升級一個普通的 Python 腳本,甚至可以將你的簡單實用程序變成成熟的 CLI 工具。

via: https://opensource.com/article/22/7/bootstrap-python-command-line-application

作者:Mark Meyer 選題:lkxed 譯者:MjSeven 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國