From b727284ef396cfd284458069d4e6901dfbb7ccc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Tue, 4 Aug 2026 15:07:49 +0800 Subject: [PATCH] Gather the CLI command modules into a commands sub-package Co-Authored-By: Claude Fable 5 --- docs/decision-log.md | 8 +++ docs/project-structure.md | 27 +++++----- .../source/pop_fem_audit_tools.commands.rst | 53 +++++++++++++++++++ tools/docs/source/pop_fem_audit_tools.rst | 48 +++-------------- tools/src/pop_fem_audit_tools/__main__.py | 22 ++++---- .../pop_fem_audit_tools/commands/__init__.py | 10 ++++ .../{ => commands}/build_db.py | 4 +- .../{ => commands}/export_llm_input.py | 4 +- .../{ => commands}/fetch_artists.py | 8 +-- .../{ => commands}/fetch_lyrics.py | 6 +-- .../{ => commands}/run_llm.py | 2 +- tools/tests/test_build_db.py | 3 +- tools/tests/test_export_llm_input.py | 3 +- tools/tests/test_fetch_artists.py | 3 +- tools/tests/test_fetch_lyrics.py | 3 +- tools/tests/test_main.py | 3 +- tools/tests/test_run_llm.py | 3 +- 17 files changed, 128 insertions(+), 82 deletions(-) create mode 100644 tools/docs/source/pop_fem_audit_tools.commands.rst create mode 100644 tools/src/pop_fem_audit_tools/commands/__init__.py rename tools/src/pop_fem_audit_tools/{ => commands}/build_db.py (99%) rename tools/src/pop_fem_audit_tools/{ => commands}/export_llm_input.py (98%) rename tools/src/pop_fem_audit_tools/{ => commands}/fetch_artists.py (99%) rename tools/src/pop_fem_audit_tools/{ => commands}/fetch_lyrics.py (99%) rename tools/src/pop_fem_audit_tools/{ => commands}/run_llm.py (99%) diff --git a/docs/decision-log.md b/docs/decision-log.md index 4541d91..4273ca6 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -245,3 +245,11 @@ 故檔案可再生,依 commit 判準不進 git(且含歌詞全文, 版權亦不許);固定匯出全部歌曲,缺歌詞即失敗;單一 位置引數收輸出檔路徑,文件範例輸出至 `tools/instance/`。 +- **指令模組集中為 `commands` sub-package**:五個子命令模組 + 移入 `pop_fem_audit_tools.commands`,`__init__` 以 + `from .run_llm import main as run_llm_command` 逐條登記為 + 指令清單,`__main__` 只消費此 façade;`_command` 後綴避免 + 與子模組同名遮蔽,測試仍以模組屬性風格使用;基礎設施模組 + (config/database/models/utils)留頂層。理由:「指令 + vs 共用底層」由目錄結構直接表達,與「哪個函式屬哪個工作」 + 的歸屬原則同型。 diff --git a/docs/project-structure.md b/docs/project-structure.md index 287ea61..0331b62 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -34,21 +34,22 @@ pop-fem-audit/ │ │ # gitignored;含歌詞全文) │ ├── src/pop_fem_audit_tools/ │ │ ├── __main__.py # 套件 CLI 進入點(分派子命令) -│ │ ├── build_db.py # build the SQLite working store -│ │ │ # from the inputs -│ │ ├── config.py # pydantic-settings 設定(.env) -│ │ ├── database.py # SQLAlchemy engine / session / Base -│ │ ├── export_llm_input.py # export the LLM input JSONL -│ │ │ # (lyrics only) from the working -│ │ │ # store -│ │ ├── fetch_artists.py # fetch artist metadata from -│ │ │ # Wikidata into the snapshot CSV -│ │ ├── fetch_lyrics.py # fetch missing lyrics from the -│ │ │ # public APIs into the lyrics dir -│ │ ├── models.py # SQLAlchemy ORM 資料模型 -│ │ ├── run_llm.py # API runner:2+1 協定、Batch API、 +│ │ ├── commands/ # CLI 子命令模組(登記於 __init__) +│ │ │ ├── build_db.py # build the SQLite working store +│ │ │ │ # from the inputs +│ │ │ ├── export_llm_input.py # export the LLM input JSONL +│ │ │ │ # (lyrics only) from the +│ │ │ │ # working store +│ │ │ ├── fetch_artists.py # fetch artist metadata from +│ │ │ │ # Wikidata into the snapshot CSV +│ │ │ ├── fetch_lyrics.py # fetch missing lyrics from the +│ │ │ │ # public APIs into the lyrics dir +│ │ │ └── run_llm.py # API runner:2+1 協定、Batch API、 │ │ │ # 寫入引數指定的 runs 目錄;執行 │ │ │ # 方式 pop-fem-audit-tools run-llm +│ │ ├── config.py # pydantic-settings 設定(.env) +│ │ ├── database.py # SQLAlchemy engine / session / Base +│ │ ├── models.py # SQLAlchemy ORM 資料模型 │ │ └── utils.py # 共用工具(format_duration) │ └── tests/ # 單元測試(unittest) ├── runs/ # 每次執行的完整稽核紀錄(進 git) diff --git a/tools/docs/source/pop_fem_audit_tools.commands.rst b/tools/docs/source/pop_fem_audit_tools.commands.rst new file mode 100644 index 0000000..d6f7471 --- /dev/null +++ b/tools/docs/source/pop_fem_audit_tools.commands.rst @@ -0,0 +1,53 @@ +pop\_fem\_audit\_tools.commands package +======================================= + +Submodules +---------- + +pop\_fem\_audit\_tools.commands.build\_db module +------------------------------------------------ + +.. automodule:: pop_fem_audit_tools.commands.build_db + :members: + :show-inheritance: + :undoc-members: + +pop\_fem\_audit\_tools.commands.export\_llm\_input module +--------------------------------------------------------- + +.. automodule:: pop_fem_audit_tools.commands.export_llm_input + :members: + :show-inheritance: + :undoc-members: + +pop\_fem\_audit\_tools.commands.fetch\_artists module +----------------------------------------------------- + +.. automodule:: pop_fem_audit_tools.commands.fetch_artists + :members: + :show-inheritance: + :undoc-members: + +pop\_fem\_audit\_tools.commands.fetch\_lyrics module +---------------------------------------------------- + +.. automodule:: pop_fem_audit_tools.commands.fetch_lyrics + :members: + :show-inheritance: + :undoc-members: + +pop\_fem\_audit\_tools.commands.run\_llm module +----------------------------------------------- + +.. automodule:: pop_fem_audit_tools.commands.run_llm + :members: + :show-inheritance: + :undoc-members: + +Module contents +--------------- + +.. automodule:: pop_fem_audit_tools.commands + :members: + :show-inheritance: + :undoc-members: diff --git a/tools/docs/source/pop_fem_audit_tools.rst b/tools/docs/source/pop_fem_audit_tools.rst index 8462ba7..0913c4d 100644 --- a/tools/docs/source/pop_fem_audit_tools.rst +++ b/tools/docs/source/pop_fem_audit_tools.rst @@ -1,17 +1,17 @@ pop\_fem\_audit\_tools package ============================== +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + pop_fem_audit_tools.commands + Submodules ---------- -pop\_fem\_audit\_tools.build\_db module ---------------------------------------- - -.. automodule:: pop_fem_audit_tools.build_db - :members: - :show-inheritance: - :undoc-members: - pop\_fem\_audit\_tools.config module ------------------------------------ @@ -28,30 +28,6 @@ pop\_fem\_audit\_tools.database module :show-inheritance: :undoc-members: -pop\_fem\_audit\_tools.export\_llm\_input module ------------------------------------------------- - -.. automodule:: pop_fem_audit_tools.export_llm_input - :members: - :show-inheritance: - :undoc-members: - -pop\_fem\_audit\_tools.fetch\_artists module --------------------------------------------- - -.. automodule:: pop_fem_audit_tools.fetch_artists - :members: - :show-inheritance: - :undoc-members: - -pop\_fem\_audit\_tools.fetch\_lyrics module -------------------------------------------- - -.. automodule:: pop_fem_audit_tools.fetch_lyrics - :members: - :show-inheritance: - :undoc-members: - pop\_fem\_audit\_tools.models module ------------------------------------ @@ -60,14 +36,6 @@ pop\_fem\_audit\_tools.models module :show-inheritance: :undoc-members: -pop\_fem\_audit\_tools.run\_llm module --------------------------------------- - -.. automodule:: pop_fem_audit_tools.run_llm - :members: - :show-inheritance: - :undoc-members: - pop\_fem\_audit\_tools.utils module ----------------------------------- diff --git a/tools/src/pop_fem_audit_tools/__main__.py b/tools/src/pop_fem_audit_tools/__main__.py index 4a1201d..5c1ae36 100644 --- a/tools/src/pop_fem_audit_tools/__main__.py +++ b/tools/src/pop_fem_audit_tools/__main__.py @@ -15,23 +15,23 @@ from collections.abc import Callable from importlib.machinery import ModuleSpec from types import ModuleType -from pop_fem_audit_tools import ( - build_db, - export_llm_input, - fetch_artists, - fetch_lyrics, - run_llm, +from .commands import ( + build_db_command, + export_llm_input_command, + fetch_artists_command, + fetch_lyrics_command, + run_llm_command, ) MODULE_PROG: str = "python -m pop_fem_audit_tools" """The program name when run with ``python -m``.""" SUBCOMMANDS: dict[str, Callable[[list[str] | None], int]] = { - "build-db": build_db.main, - "export-llm-input": export_llm_input.main, - "fetch-artists": fetch_artists.main, - "fetch-lyrics": fetch_lyrics.main, - "run-llm": run_llm.main, + "build-db": build_db_command, + "export-llm-input": export_llm_input_command, + "fetch-artists": fetch_artists_command, + "fetch-lyrics": fetch_lyrics_command, + "run-llm": run_llm_command, } """The dispatch table from the subcommand name to the tool main.""" diff --git a/tools/src/pop_fem_audit_tools/commands/__init__.py b/tools/src/pop_fem_audit_tools/commands/__init__.py new file mode 100644 index 0000000..5a3065d --- /dev/null +++ b/tools/src/pop_fem_audit_tools/commands/__init__.py @@ -0,0 +1,10 @@ +# Tools for A Feminist Audit of Pop Music. +# Copyright 2026 imacat. All rights reserved. +# Authors: +# imacat@mail.imacat.idv.tw (imacat), 2026/8/4 +"""The registry of the CLI subcommands.""" +from .build_db import main as build_db_command +from .export_llm_input import main as export_llm_input_command +from .fetch_artists import main as fetch_artists_command +from .fetch_lyrics import main as fetch_lyrics_command +from .run_llm import main as run_llm_command diff --git a/tools/src/pop_fem_audit_tools/build_db.py b/tools/src/pop_fem_audit_tools/commands/build_db.py similarity index 99% rename from tools/src/pop_fem_audit_tools/build_db.py rename to tools/src/pop_fem_audit_tools/commands/build_db.py index 6489e7a..34ebe5f 100644 --- a/tools/src/pop_fem_audit_tools/build_db.py +++ b/tools/src/pop_fem_audit_tools/commands/build_db.py @@ -59,8 +59,8 @@ from typing import Any, Self import sqlalchemy as sa from sqlalchemy.orm import Session -from .database import Base, ds -from .models import ( +from ..database import Base, ds +from ..models import ( Artist, ChartEntry, Role, diff --git a/tools/src/pop_fem_audit_tools/export_llm_input.py b/tools/src/pop_fem_audit_tools/commands/export_llm_input.py similarity index 98% rename from tools/src/pop_fem_audit_tools/export_llm_input.py rename to tools/src/pop_fem_audit_tools/commands/export_llm_input.py index 524d605..a39905d 100644 --- a/tools/src/pop_fem_audit_tools/export_llm_input.py +++ b/tools/src/pop_fem_audit_tools/commands/export_llm_input.py @@ -19,8 +19,8 @@ from pathlib import Path import sqlalchemy as sa from sqlalchemy.orm import Session -from .database import ds -from .models import Song +from ..database import ds +from ..models import Song def parse_args(argv: list[str] | None) -> argparse.Namespace: diff --git a/tools/src/pop_fem_audit_tools/fetch_artists.py b/tools/src/pop_fem_audit_tools/commands/fetch_artists.py similarity index 99% rename from tools/src/pop_fem_audit_tools/fetch_artists.py rename to tools/src/pop_fem_audit_tools/commands/fetch_artists.py index 02c4542..16278fc 100644 --- a/tools/src/pop_fem_audit_tools/fetch_artists.py +++ b/tools/src/pop_fem_audit_tools/commands/fetch_artists.py @@ -34,10 +34,10 @@ from typing import Any, Literal, TextIO import sqlalchemy as sa from sqlalchemy.orm import Session -from . import VERSION -from .database import ds -from .models import Artist, Song, SongArtist -from .utils import format_duration +from .. import VERSION +from ..database import ds +from ..models import Artist, Song, SongArtist +from ..utils import format_duration API_URL: str = "https://www.wikidata.org/w/api.php" """The URL of the Wikidata API endpoint.""" diff --git a/tools/src/pop_fem_audit_tools/fetch_lyrics.py b/tools/src/pop_fem_audit_tools/commands/fetch_lyrics.py similarity index 99% rename from tools/src/pop_fem_audit_tools/fetch_lyrics.py rename to tools/src/pop_fem_audit_tools/commands/fetch_lyrics.py index acd86db..7a607e9 100644 --- a/tools/src/pop_fem_audit_tools/fetch_lyrics.py +++ b/tools/src/pop_fem_audit_tools/commands/fetch_lyrics.py @@ -36,14 +36,14 @@ from typing import Any import sqlalchemy as sa from sqlalchemy.orm import Session -from .database import ds -from .models import ( +from ..database import ds +from ..models import ( Artist, Role, Song, SongArtist, ) -from .utils import format_duration +from ..utils import format_duration PROVENANCE_FIELDS: Sequence[str] = ( "song_id", "source", "method", "acquired_at", "note") diff --git a/tools/src/pop_fem_audit_tools/run_llm.py b/tools/src/pop_fem_audit_tools/commands/run_llm.py similarity index 99% rename from tools/src/pop_fem_audit_tools/run_llm.py rename to tools/src/pop_fem_audit_tools/commands/run_llm.py index 9ce6f74..9fcc000 100644 --- a/tools/src/pop_fem_audit_tools/run_llm.py +++ b/tools/src/pop_fem_audit_tools/commands/run_llm.py @@ -26,7 +26,7 @@ from typing import Any, Self import anthropic -from .config import get_settings +from ..config import get_settings MODEL: str = "claude-sonnet-4-6" TEMPERATURE: float = 0.0 diff --git a/tools/tests/test_build_db.py b/tools/tests/test_build_db.py index 4c1beb2..506151a 100644 --- a/tools/tests/test_build_db.py +++ b/tools/tests/test_build_db.py @@ -15,7 +15,8 @@ from unittest import mock import sqlalchemy as sa from sqlalchemy.orm import Session -from pop_fem_audit_tools import build_db, config +from pop_fem_audit_tools import config +from pop_fem_audit_tools.commands import build_db from pop_fem_audit_tools.database import DataSource from pop_fem_audit_tools.models import ( Artist, diff --git a/tools/tests/test_export_llm_input.py b/tools/tests/test_export_llm_input.py index 6c224af..9181264 100644 --- a/tools/tests/test_export_llm_input.py +++ b/tools/tests/test_export_llm_input.py @@ -14,7 +14,8 @@ from unittest import mock from sqlalchemy.orm import Session -from pop_fem_audit_tools import config, export_llm_input +from pop_fem_audit_tools import config +from pop_fem_audit_tools.commands import export_llm_input from pop_fem_audit_tools.database import Base, DataSource from pop_fem_audit_tools.models import ( Artist, diff --git a/tools/tests/test_fetch_artists.py b/tools/tests/test_fetch_artists.py index dac99df..8441d05 100644 --- a/tools/tests/test_fetch_artists.py +++ b/tools/tests/test_fetch_artists.py @@ -16,7 +16,8 @@ from unittest import mock from sqlalchemy.orm import Session -from pop_fem_audit_tools import config, fetch_artists +from pop_fem_audit_tools import config +from pop_fem_audit_tools.commands import fetch_artists from pop_fem_audit_tools.database import Base, DataSource from pop_fem_audit_tools.models import Artist, Role, Song, SongArtist diff --git a/tools/tests/test_fetch_lyrics.py b/tools/tests/test_fetch_lyrics.py index 07e908f..0744b94 100644 --- a/tools/tests/test_fetch_lyrics.py +++ b/tools/tests/test_fetch_lyrics.py @@ -16,7 +16,8 @@ from unittest import mock from sqlalchemy.orm import Session -from pop_fem_audit_tools import config, fetch_lyrics +from pop_fem_audit_tools import config +from pop_fem_audit_tools.commands import fetch_lyrics from pop_fem_audit_tools.database import Base, DataSource from pop_fem_audit_tools.models import ( Artist, diff --git a/tools/tests/test_main.py b/tools/tests/test_main.py index e622750..d0f412e 100644 --- a/tools/tests/test_main.py +++ b/tools/tests/test_main.py @@ -11,7 +11,8 @@ import unittest from contextlib import redirect_stderr, redirect_stdout from unittest import mock -from pop_fem_audit_tools import __main__, run_llm +from pop_fem_audit_tools import __main__ +from pop_fem_audit_tools.commands import run_llm class TestDispatcher(unittest.TestCase): diff --git a/tools/tests/test_run_llm.py b/tools/tests/test_run_llm.py index 76f5a60..1c937bf 100644 --- a/tools/tests/test_run_llm.py +++ b/tools/tests/test_run_llm.py @@ -14,7 +14,8 @@ from pathlib import Path from typing import Any from unittest import mock -from pop_fem_audit_tools import config, run_llm +from pop_fem_audit_tools import config +from pop_fem_audit_tools.commands import run_llm class RunLLMTestCase(unittest.TestCase):