首頁 > 軟體

Python實現指令碼轉換為命令列程式

2022-09-26 14:04:59

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

首先,你需要安裝 ​ ​scaffold​ ​​、​ ​click​ ​​ 和 ​ ​tox​ ​​ ​ ​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.cfg​ ​​ 的 ​ ​options.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​ ​​。測試使用它在一個隔離的檔案系統中執行此命令。然後測試在隔離的檔案系統中建立 ​ ​incoming​ ​​ 和 ​ ​archive​ ​​ 目錄和一個虛擬的 ​ ​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​ ​ 只是取消了最新檔案的連結,無論它是否存在。測試從一個新的檔案系統(不是我的主資料夾)開始,很快就失敗了。我可以通過在一個很好的隔離和自動化測試環境中執行來防止這種錯誤。這將避免很多“它在我的機器上正常工作”的問題。

搭建骨架指令碼和模組

我們可以使用 ​ ​scaffold​ ​​ 和 ​ ​click​ ​ 完成一些高階操作。有很多方法可以升級一個普通的 Python 指令碼,甚至可以將你的簡單實用程式變成成熟的 CLI 工具。

以上就是Python實現指令碼轉換為命令列程式的詳細內容,更多關於Python指令碼轉命令列的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com