[Python 시리즈 12편] CLI 자동화 앱으로 연결하기

English version

이제까지 다룬 클래스 설계, 예외 처리, 프로젝트 위생, 테스트를 하나로 묶어보겠습니다. 이 12편은 시리즈 전반부를 마감하고 심화 문법으로 넘어가기 위한 체크포인트입니다. Typer 기반 CLI를 만들어 일상적인 운영 업무를 자동화하며, 곧 등장할 컴프리헨션·데코레이터·타입 힌트 학습과 자연스럽게 연결되는 기반을 마련합니다.

이번 글에서 새로 나오는 용어

  1. Typer: 함수와 타입 힌트만으로 명령과 옵션을 선언하게 해 주는 Python CLI 프레임워크
  2. 서브커맨드: mealbot meal처럼 CLI 안에서 기능별로 나뉜 하위 명령
  3. 종료 코드: 프로그램이 끝날 때 운영체제에 돌려주는 숫자로 0은 성공, 1 이상은 실패를 뜻함

개념 정리

학습 메모

  • 소요 시간: 70~80분
  • 준비물: requests·파일 IO, 예외 처리·로깅, pytest
  • 학습 목표: Typer CLI를 만들고 예외·로그·테스트 흐름을 한 명령으로 묶기
  • CLI(Command Line Interface)는 명령어로 실행하는 앱입니다.
  • Typer는 타입 힌트를 읽어 도움말을 자동으로 생성하는 CLI 프레임워크입니다.
  • typer.Exit는 종료 코드를 제어해 스케줄러와 CI가 실패를 감지하게 합니다.

코드로 이해하기

Typer 설치와 기본 명령

uv add typer rich

src/mealbot/cli.py를 만들고 아래처럼 작성합니다.

🧭 Typer란? Click 위에 구축된 Python CLI 프레임워크입니다. 함수에 데코레이터를 붙여 명령을 선언하고, 타입 힌트로 자동 도움말을 생성할 수 있습니다.

from mealbot.fetcher import fetch_meal
from mealbot.notifier import SlackNotifier

app = typer.Typer(help="급식/공지 자동화 도구")


@app.command()
def meal(school_code: str, webhook: str):
    """오늘의 급식을 조회해 Slack으로 보냅니다."""
    data = fetch_meal(school_code)
    SlackNotifier(webhook).notify(
        "오늘 급식: " + ", ".join(data["menu"])
    )
    typer.echo("전송 완료")


if __name__ == "__main__":
    app()

예외 처리와 로그 통합

from mealbot.errors import MealFetchError


@app.callback()
def configure_logging(verbose: bool = typer.Option(False, "-v", help="디버그 로그 출력")):
    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(level=level, format="%(asctime)s %(levelname)s %(message)s")


@app.command()
def meal(...):
    try:
        ...
    except MealFetchError:
        logging.exception("급식 조회 실패")
        raise typer.Exit(code=1)

명령 실행이 실패하면 typer.Exit로 종료 코드를 설정해 CI나 스케줄러가 장애를 감지할 수 있게 합니다.

서브커맨드 확장

@app.command()
def notify(status: str, webhook: str):
    notifier = SlackNotifier(webhook)
    notifier.notify(f"배포 상태: {status}")


@app.command()
def generate_report(days: int = 7):
    typer.echo(f"최근 {days}일 데이터를 CSV로 저장")

운영 작업은 대부분 입력 파라미터만 다른 반복 업무입니다. Typer의 서브커맨드로 자연스럽게 그룹화할 수 있습니다.

패키지 스크립트 등록

pyproject.toml[project.scripts] 섹션을 추가합니다.

[project.scripts]
mealbot = "mealbot.cli:app"

이제 uv run mealbot meal --school-code demo-school --webhook ...처럼 실행할 수 있고, 서버에는 pipxuv tool install로 배포해 반복 작업을 CLI로 대체할 수 있습니다.

테스트와 배포 체크

uv run pytest
uv run mealbot meal --school-code demo-school --webhook $WEBHOOK_URL

테스트로 기본 로직을 검증하고, CLI 명령은 스테이징 웹훅을 향해 실제 호출을 한 번 실행해 봅니다. GitHub Actions에서는 cron 워크플로로 CLI를 구동해 하루치 데이터 보고서를 생성하도록 할 수 있습니다.

기대 출력 확인

CLI는 결과 화면이 일정해야 디버깅과 공유가 쉽습니다.

:::terminal{title="mealbot 실행 결과 예시", showFinalPrompt="false"}

[
  { "cmd": "uv run mealbot meal --school-code demo-school --webhook https://hooks.test/demo", "output": "2026-03-17 07:30:10 INFO 급식 조회 시작\n2026-03-17 07:30:10 INFO menu=['잡곡밥', '된장찌개', '불고기']\n2026-03-17 07:30:11 INFO Slack 전송 완료\n전송 완료", "delay": 500 },
  { "cmd": "uv run mealbot meal --school-code wrong-school --webhook https://hooks.test/demo", "output": "2026-03-17 07:31:02 ERROR 급식 조회 실패\nTraceback (most recent call last): ...\nError: school not found\nExited with code 1", "delay": 500 }
]

:::

  • 확인할 점: 성공 시에는 완료 메시지가 마지막에 남는지
  • 확인할 점: 실패 시에는 오류 로그와 종료 코드가 함께 남는지
  • 확인할 점: 같은 명령을 여러 번 실행해도 형식이 흔들리지 않는지

왜 중요할까

CLI는 터미널에서 반복 업무를 한 줄로 실행하게 해 줍니다. Typer로 명령을 만들고 예외·로그를 묶어 두면 학교나 스터디 운영에서 필요한 알림·보고서 작업을 자동으로 돌릴 수 있습니다. 스크립트 등록까지 끝내면 어디서든 같은 명령어로 동작을 재현할 수 있습니다.

실습

  • 따라 하기: meal 명령을 그대로 작성하고 uv run mealbot meal ...로 성공 메시지를 확인합니다.
  • 확장하기: 새로운 report 서브커맨드를 추가해 JSON 파일을 읽어 요약을 출력하고 [project.scripts]에 등록합니다.
  • 디버깅: 일부러 잘못된 웹훅을 넣어 예외를 발생시키고 typer.Exit 코드와 로그 메시지를 확인한 뒤 복구합니다.
  • 완료 기준: CLI 명령 실행 로그, 실패 시 종료 코드, pytest 결과까지 한 문서에 정리해 다음 세션에서 바로 이어갈 수 있을 때입니다.

마무리

이제 시리즈의 전환점을 지났습니다. CLI 로직에 모든 기초를 연결해 두었으니, 13~19편에서는 이 구조를 더 강력하게 만드는 심화 문법(컴프리헨션, 데코레이터, 컨텍스트 매니저, 타입 힌트, 비동기, 표준 라이브러리 활용)을 차례대로 얹게 됩니다. 각 챕터는 여기서 만든 CLI와 데이터를 계속 참조하며 누적되는 구조를 유지합니다. 마지막 20편의 진짜 캡스톤 프로젝트는 지금 준비한 CLI 뼈대를 다시 불러와 전체 시리즈를 종합할 예정이니, 이번 편의 코드를 다음 편 따라 하기 폴더에 그대로 두고 이어가세요.

💬 댓글

이 글에 대한 의견을 남겨주세요