본문으로 건너뛰기
← 블로그로 돌아가기
튜토리얼 2026년 1월 30일

Python CLI 도구 — argparse부터 배포까지

Python으로 실용적인 CLI 도구를 만들고 pip로 배포하는 전체 과정.

Python CLI argparse pip

왜 CLI인가

GUI가 필요 없는 자동화 도구는 CLI가 가장 효율적이다. PromoEngine을 만들 때 GUI를 고려했지만, 주 사용자가 개발자이고 스크립트에서 호출할 수 있어야 했다. CLI가 정답이었다.

argparse 기본

Python 표준 라이브러리의 argparse로 시작한다.

import argparse

def main():
    parser = argparse.ArgumentParser(
        prog='promo',
        description='멀티채널 콘텐츠 자동 생성 CLI'
    )
    subparsers = parser.add_subparsers(dest='command')

    # generate 서브커맨드
    gen = subparsers.add_parser('generate', help='콘텐츠 생성')
    gen.add_argument('--source', '-s', required=True, help='원본 파일 경로')
    gen.add_argument('--channel', '-c', default='all', help='대상 채널 (쉼표 구분)')
    gen.add_argument('--dry-run', action='store_true', help='API 호출 없이 프롬프트만 출력')

    # list 서브커맨드
    ls = subparsers.add_parser('list', help='생성 이력 조회')
    ls.add_argument('--project', '-p', help='프로젝트 이름으로 필터')

    args = parser.parse_args()

    if args.command == 'generate':
        handle_generate(args)
    elif args.command == 'list':
        handle_list(args)
    else:
        parser.print_help()

서브커맨드 패턴(git style)을 사용하면 기능이 늘어나도 구조가 깔끔하다.

설정 파일

API 키나 기본값을 매번 인자로 전달하는 건 번거롭다. TOML 설정 파일을 지원한다.

# ~/.promo/config.toml
[api]
provider = "anthropic"
model = "claude-sonnet-4-20250514"

[defaults]
channels = ["reddit", "twitter", "blog"]
language = "ko"
import tomllib
from pathlib import Path

def load_config() -> dict:
    config_path = Path.home() / '.promo' / 'config.toml'
    if config_path.exists():
        with open(config_path, 'rb') as f:
            return tomllib.load(f)
    return {}

Python 3.11+의 tomllib을 사용한다. 외부 패키지가 필요 없다.

출력 포맷팅

CLI의 출력은 가독성이 중요하다. rich 라이브러리로 테이블과 색상을 적용한다.

from rich.console import Console
from rich.table import Table

console = Console()

def show_results(results: dict):
    table = Table(title="생성 결과")
    table.add_column("채널", style="cyan")
    table.add_column("글자수", justify="right")
    table.add_column("상태", style="green")

    for channel, content in results.items():
        table.add_row(channel, str(len(content)), "완료")

    console.print(table)

에러 처리

CLI에서 에러 메시지는 사용자가 다음에 무엇을 해야 하는지 알려줘야 한다.

import sys

def handle_generate(args):
    source = Path(args.source)
    if not source.exists():
        console.print(f"[red]파일을 찾을 수 없습니다: {source}[/red]")
        console.print("[dim]--source에 올바른 파일 경로를 지정하세요[/dim]")
        sys.exit(1)

    config = load_config()
    if not config.get('api', {}).get('key'):
        console.print("[red]API 키가 설정되지 않았습니다[/red]")
        console.print("[dim]~/.promo/config.toml에 api.key를 설정하세요[/dim]")
        sys.exit(1)

패키징과 배포

pyproject.toml로 패키지를 정의하고 pip로 설치할 수 있게 만든다.

[project]
name = "promoengine"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "anthropic>=0.40.0",
    "rich>=13.0",
]

[project.scripts]
promo = "promoengine.cli:main"

[project.scripts]가 핵심이다. pip install . 후 터미널에서 promo 명령을 바로 쓸 수 있다.

# 로컬 설치
pip install -e .

# PyPI 배포
pip install build twine
python -m build
twine upload dist/*

테스트

CLI 테스트는 subprocess보다 직접 함수를 호출하는 게 낫다.

def test_generate_dry_run():
    args = argparse.Namespace(
        source='fixtures/sample.md',
        channel='reddit',
        dry_run=True,
    )
    result = handle_generate(args)
    assert 'reddit' in result
    assert len(result['reddit']) > 0

--dry-run 플래그는 테스트에서도 유용하다. API 비용 없이 프롬프트 생성 로직을 검증할 수 있다.

정리

Python CLI 도구를 만드는 흐름: argparse로 인터페이스 정의 → TOML로 설정 관리 → rich로 출력 포맷팅 → pyproject.toml로 패키징. 이 패턴을 따르면 대부분의 CLI 도구를 깔끔하게 만들 수 있다.