Python 언어별 가이드

Python 언어별 가이드

이 가이드는 Docker로 Python 어플리케이션을 컨테이너화하는 방법을 알려줘요.

출처: 문서

본문

감사의 말 (Acknowledgment)

이 가이드는 커뮤니티 기여예요. Docker는 이 가이드에 기여해준 Esteban Maya와 Igor Aleksandrov에게 감사를 전해요.

Python 언어별 가이드는 Docker를 사용해 Python 어플리케이션을 컨테이너화하는 방법을 가르쳐줘요. 이 가이드에서 다음을 배우게 돼요:

  • Python 어플리케이션 컨테이너화하고 실행하기
  • 컨테이너를 사용해 Python 어플리케이션을 개발할 로컬 환경 구성하기
  • 린트(lint), 포맷, 타이핑(typing)과 모범 사례

기존 Python 어플리케이션을 컨테이너화하는 것부터 시작해요.

Python 어플리케이션 컨테이너화하기

준비 사항 (Prerequisites)

개요 (Overview)

어플리케이션을 컨테이너화하는 것은 어플리케이션을 그 의존성, 구성, 런타임과 함께 컨테이너 이미지라는 단일 이식 가능한 단위로 패키징하는 것을 의미해요. 그 이미지를 실행하면 컨테이너가 만들어지는데, 랩톱, CI 러너, 프로덕션 서버 등 어떤 머신에서든 동일하게 동작하는 격리된 프로세스예요.

이 섹션에서는 단순한 FastAPI 웹 어플리케이션을 컨테이너화해요. 이미지를 빌드하는 방법을 설명하는 Dockerfile을 작성하고, Docker가 컨테이너를 실행하는 방법을 정의하는 compose.yaml 파일을 추가한 다음, 한 명령으로 어플리케이션을 빌드·시작할 거예요.

베이스로 Docker Hardened Images를 사용할 거예요. Docker가 관리하는 최소화되고 안전한 Python 이미지예요.

어플리케이션 만들기

샘플 어플리케이션은 단일 엔드포인트가 JSON 인사말을 반환하는 최소한의 FastAPI 서비스예요. 새 python-docker-example 디렉토리에 다음 파일들을 만들어요. 파일을 모두 한 번에 만들려면 파일 브라우저에서 Scaffold script 탭으로 전환해 셸 명령을 복사해요.

python-docker-example/app.py (새 파일):

# A minimal FastAPI application.
# The root endpoint (GET /) returns a JSON "Hello World" response.
# See https://fastapi.tiangolo.com/ for the framework reference.

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

python-docker-example/requirements.txt (새 파일):

# Python package dependencies for the application, pinned for reproducible builds.
# See https://pip.pypa.io/en/stable/reference/requirements-file-format/

fastapi==0.115.12
uvicorn==0.34.3

python-docker-example/.gitignore (새 파일):

# Files and directories that Git should ignore. This is the standard Python
# template covering bytecode, build artifacts, virtual environments, and IDE
# settings. See https://git-scm.com/docs/gitignore for syntax reference.

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Secrets
db/password.txt

Note

이 섹션의 Bash/PowerShell 탭에는 Scaffold 스크립트가 있어 위 파일들을 한 번에 만들 수 있어요.

Python이 이미 설치되어 있고 컨테이너화하기 전에 앱이 동작하는지 확인하고 싶다면 로컬에서 실행할 수 있어요:

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt
$ uvicorn app:app --reload

Note

Windows에서는 source .venv/bin/activate 대신 .venv\Scripts\activate로 가상 환경을 활성화해요.

Python이 설치되어 있지 않다면 다음 섹션으로 건너뛰어도 돼요. 나머지 단계들은 로컬 Python 없이 컨테이너에서 어플리케이션을 실행해요.

Docker 자산 만들기 (Create the Docker assets)

빌드 중에 Docker가 Python 베이스 이미지를 pull할 수 있도록 DHI 레지스트리에 로그인해요. 사용 가능한 Python 이미지들은 catalog에 나열돼 있어요.

$ docker login dhi.io

python-docker-example 디렉토리에 다음 세 파일을 추가해요. Dockerfile은 이미지를 빌드하는 방법을 설명하고, compose.yaml은 Docker가 컨테이너를 실행하는 방법을 정의하며, .dockerignore는 원하지 않는 파일을 빌드 컨텍스트에서 제외해요.

Tip

Gordon, Docker의 AI 어시스턴트가 프로젝트에 맞는 Docker 자산을 생성해줄 수 있어요. Gordon에게 어플리케이션에 맞춘 Dockerfile, Compose 파일, .dockerignore를 만들어 달라고 요청해보세요.

python-docker-example/Dockerfile (새 파일):

# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

# This Dockerfile uses Docker Hardened Images (DHI) for enhanced security.
# For more information, see https://docs.docker.com/dhi/

# Use the dev image to build and install dependencies.
FROM dhi.io/python:3.12-dev AS builder

WORKDIR /app

RUN python3 -m venv /venv
ENV PATH="/venv/bin:$PATH"

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them into
# this layer.
RUN --mount=type=cache,target=/root/.cache/pip \
    --mount=type=bind,source=requirements.txt,target=requirements.txt \
    pip install -r requirements.txt

# Use the minimal runtime image. It runs as nonroot by default.
FROM dhi.io/python:3.12

WORKDIR /app

COPY --from=builder /venv /venv
ENV PATH="/venv/bin:$PATH"

# Copy the source code into the container.
COPY . .

# Expose the port that the application listens on.
EXPOSE 8000

# Run the application.
CMD ["/venv/bin/python3", "-m", "uvicorn", "app:app", "--host=0.0.0.0", "--port=8000"]

python-docker-example/compose.yaml (새 파일):

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker Compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/

# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
  server:
    build:
      context: .
    ports:
      - 8000:8000

python-docker-example/.dockerignore (새 파일):

# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/

**/.DS_Store
**/__pycache__
**/.venv
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose.y*ml
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

각 파일에 대해 더 알아보려면 다음을 참고해요:

어플리케이션 실행하기 (Run the application)

python-docker-example 디렉토리 안에서 터미널에 다음 명령을 실행해요.

$ docker compose up --build

브라우저를 열고 http://localhost:8000에서 어플리케이션을 확인해요. 간단한 FastAPI 어플리케이션을 볼 수 있어요.

터미널에서 ctrl+c를 눌러 어플리케이션을 중지해요.

어플리케이션을 백그라운드로 실행하기 (Run the application in the background)

-d 옵션을 추가하면 터미널에서 분리된 상태로 어플리케이션을 실행할 수 있어요. python-docker-example 디렉토리 안에서 터미널에 다음 명령을 실행해요.

$ docker compose up --build -d

브라우저를 열고 http://localhost:8000에서 어플리케이션을 확인해요.

OpenAPI 문서를 보려면 http://localhost:8000/docs로 가도 돼요.

간단한 FastAPI 어플리케이션을 볼 수 있어요.

터미널에서 다음 명령을 실행해 어플리케이션을 중지해요.

$ docker compose down

Compose 명령에 대한 자세한 내용은 Compose CLI reference를 참고해요.

Python 개발에 컨테이너 사용하기 (Use containers for Python development)

준비 사항 (Prerequisites)

Python 어플리케이션 컨테이너화를 완료해요.

개요 (Overview)

어플리케이션이 컨테이너에서 실행되면 다음 단계는 컨테이너 루프를 일상적인 개발 워크플로의 일부로 만드는 거예요. 코드 변경이 빠르게 반영되어야 하고, 데이터베이스처럼 앱이 의존하는 서비스도 바로 옆에서 실행되어야 해요.

이 섹션에서는 compose.yaml에 PostgreSQL 데이터베이스 서비스를 추가하고, 데이터베이스 데이터를 명명된 볼륨에 유지하며, Compose Watch를 활성화해서 편집기에 저장한 변경 사항이 수동 리빌드 없이 실행 중인 컨테이너에 반영되도록 이전 주제의 프로젝트를 확장할 거예요.

어플리케이션 업데이트하기 (Update the application)

어플리케이션을 PostgreSQL 데이터베이스에 연결하도록 업데이트할 거예요. python-docker-example 디렉토리에서 계속 작업해요.

app.py와 requirements.txt를 교체하고, 다음 내용으로 새 config.py 파일을 추가해요.

Note

이 단계 후에는 어플리케이션이 아직 실행되지 않아요. 존재하지 않는 PostgreSQL 데이터베이스에 연결하려고 하기 때문이에요. 다음 두 섹션에서 데이터베이스 서비스와 모든 것을 함께 실행하는 데 필요한 Docker 구성을 추가해요.

python-docker-example/app.py (수정됨):

# FastAPI application backed by a PostgreSQL database via SQLModel.
# The FastAPI lifespan handler creates database tables at startup.
# Endpoints: GET / (greeting), POST /heroes/ (create), GET /heroes/ (list).
# See https://fastapi.tiangolo.com/ and https://sqlmodel.tiangolo.com/

from collections.abc import AsyncGenerator, Sequence
from contextlib import asynccontextmanager

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select

from config import settings


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))


def create_db_and_tables() -> None:
    SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
    create_db_and_tables()
    yield


app = FastAPI(lifespan=lifespan)


@app.get("/")
def hello() -> str:
    return "Hello, Docker!"


@app.post("/heroes/")
def create_hero(hero: Hero) -> Hero:
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/")
def read_heroes() -> Sequence[Hero]:
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

python-docker-example/config.py (새 파일):

# Pydantic settings that read PostgreSQL connection details from the
# environment. Supports a password file (Docker secrets) via
# POSTGRES_PASSWORD_FILE in addition to POSTGRES_PASSWORD.
# See https://docs.pydantic.dev/latest/concepts/pydantic_settings/

import os
from typing import Any

from pydantic import (
    PostgresDsn,
    computed_field,
    field_validator,
    model_validator,
)
from pydantic_core import MultiHostUrl
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    POSTGRES_SERVER: str
    POSTGRES_PORT: int = 5432
    POSTGRES_USER: str
    POSTGRES_PASSWORD: str | None = None
    POSTGRES_PASSWORD_FILE: str | None = None
    POSTGRES_DB: str

    @model_validator(mode="before")
    @classmethod
    def check_postgres_password(cls, data: Any) -> Any:
        """Validate that either POSTGRES_PASSWORD or POSTGRES_PASSWORD_FILE is set."""
        if isinstance(data, dict):
            password_file: str | None = data.get("POSTGRES_PASSWORD_FILE")  # type: ignore
            password: str | None = data.get("POSTGRES_PASSWORD")  # type: ignore
            if password_file is None and password is None:
                raise ValueError(
                    "At least one of POSTGRES_PASSWORD_FILE and POSTGRES_PASSWORD must be set."
                )
        return data  # type: ignore

    @field_validator("POSTGRES_PASSWORD_FILE", mode="before")
    @classmethod
    def read_password_from_file(cls, v: str | None) -> str | None:
        if v is not None:
            file_path = v
            if os.path.exists(file_path):
                with open(file_path) as file:
                    return file.read().strip()
            raise ValueError(f"Password file {file_path} does not exist.")
        return v

    @computed_field
    @property
    def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn:
        url = MultiHostUrl.build(
            scheme="postgresql+psycopg",
            username=self.POSTGRES_USER,
            password=self.POSTGRES_PASSWORD
            if self.POSTGRES_PASSWORD
            else self.POSTGRES_PASSWORD_FILE,
            host=self.POSTGRES_SERVER,
            port=self.POSTGRES_PORT,
            path=self.POSTGRES_DB,
        )
        return PostgresDsn(url)


settings = Settings()  # type: ignore

python-docker-example/requirements.txt (수정됨):

# Python package dependencies for the application, pinned for reproducible builds.
# See https://pip.pypa.io/en/stable/reference/requirements-file-format/

fastapi==0.115.12
sqlmodel==0.0.24
psycopg[binary]==3.2.9
pydantic-settings==2.9.1
uvicorn==0.34.3

Docker 자산 업데이트하기 (Update Docker assets)

Dockerfile과 compose.yaml을 다음으로 교체해요.

python-docker-example/Dockerfile (수정됨):

# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

# This Dockerfile uses Docker Hardened Images (DHI) for enhanced security.
# For more information, see https://docs.docker.com/dhi/

# Use the dev image to build and install dependencies.
# The builder stage is also used directly in development (see compose.yaml).
FROM dhi.io/python:3.12-dev AS builder

WORKDIR /app

RUN python3 -m venv /venv
ENV PATH="/venv/bin:$PATH"

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them
# into this layer.
RUN --mount=type=cache,target=/root/.cache/pip \
    --mount=type=bind,source=requirements.txt,target=requirements.txt \
    pip install -r requirements.txt

# Copy the source code into the container.
COPY . .

# Expose the port that the application listens on.
EXPOSE 8000

# Run the application.
CMD ["/venv/bin/python3", "-m", "uvicorn", "app:app", "--host=0.0.0.0", "--port=8000"]

# Use the minimal runtime image for production. It runs as nonroot by default.
FROM dhi.io/python:3.12

WORKDIR /app

COPY --from=builder /venv /venv
ENV PATH="/venv/bin:$PATH"

COPY --from=builder /app .

EXPOSE 8000

CMD ["/venv/bin/python3", "-m", "uvicorn", "app:app", "--host=0.0.0.0", "--port=8000"]

python-docker-example/compose.yaml (수정됨):

services:
  # Application service. The `target: builder` line builds the development
  # image (includes a shell and tools); the production stage of the
  # Dockerfile is unused in development.
  server:
    build:
      context: .
      target: builder
    ports:
      - 8000:8000

변경 사항에 대해 (About these changes)

builder 스테이지는 개발 중에도 직접 사용돼요 (compose.yaml에서 target: builder). builder 스테이지는 셸과 도구를 포함하는 개발 이미지이고, 프로덕션 스테이지(FROM dhi.io/python:3.12)는 개발 중에는 사용되지 않아요.

로컬 데이터베이스 추가 및 데이터 유지 (Add a local database and persist data)

컨테이너를 사용해 데이터베이스 같은 로컬 서비스를 구성할 수 있어요. 이 섹션에서는 compose.yaml 파일을 수정해 데이터베이스 서비스와 데이터를 유지할 볼륨을 정의하고, 데이터베이스 비밀번호를 담은 db/password.txt 파일을 추가할 거예요.

python-docker-example/compose.yaml (수정됨):

services:
  # Application service. The `target: builder` line builds the development
  # image (includes a shell and tools); the production stage of the
  # Dockerfile is unused in development.
  server:
    build:
      context: .
      target: builder
    ports:
      - 8000:8000
    environment:
      - POSTGRES_SERVER=db
      - POSTGRES_USER=postgres
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password

  # Database service. Reads the password from a Docker secret mounted at
  # /run/secrets/db-password. Compose waits for the healthcheck to pass
  # before starting the server, via the server's depends_on.
  db:
    image: dhi.io/postgres:18
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db-data:

secrets:
  db-password:
    file: db/password.txt

python-docker-example/db/password.txt (새 파일):

mysecretpassword

Note

Compose 파일의 지시사항에 대해 더 알아보려면 Compose file reference를 참고해요.

이제 다음 docker compose up 명령을 실행해 어플리케이션을 시작해요.

$ docker compose up --build

이제 API 엔드포인트를 테스트해요. 새 터미널을 열고 curl 명령으로 서버에 요청을 보내요:

POST 요청으로 객체를 만들어요:

$ curl -X 'POST' \
  'http://localhost:8000/heroes/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 1,
  "name": "my hero",
  "secret_name": "austing",
  "age": 12
}'

다음과 같은 응답을 받게 돼요:

{
  "age": 12,
  "id": 1,
  "name": "my hero",
  "secret_name": "austing"
}

이제 GET 요청을 보내요:

$ curl -X 'GET' \
  'http://localhost:8000/heroes/' \
  -H 'accept: application/json'

데이터베이스의 유일한 객체이므로 위와 같은 응답을 받게 돼요.

{
  "age": 12,
  "id": 1,
  "name": "my hero",
  "secret_name": "austing"
}

터미널에서 ctrl+c를 눌러 어플리케이션을 중지해요.

서비스 자동 업데이트 (Automatically update services)

Compose Watch를 사용하면 코드를 수정·저장할 때 실행 중인 Compose 서비스를 자동으로 갱신할 수 있어요. Compose Watch에 대한 자세한 내용은 Use Compose Watch를 참고해요.

compose.yaml 파일을 IDE나 텍스트 편집기로 열고 강조 표시된 Compose Watch 지시사항을 추가해요.

python-docker-example/compose.yaml (수정됨):

services:
  # Application service. The `target: builder` line builds the development
  # image (includes a shell and tools); the production stage of the
  # Dockerfile is unused in development.
  server:
    build:
      context: .
      target: builder
    ports:
      - 8000:8000
    environment:
      - POSTGRES_SERVER=db
      - POSTGRES_USER=postgres
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
    develop:
      watch:
        - action: rebuild
          path: .

  # Database service. Reads the password from a Docker secret mounted at
  # /run/secrets/db-password. Compose waits for the healthcheck to pass
  # before starting the server, via the server's depends_on.
  db:
    image: dhi.io/postgres:18
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db-data:

secrets:
  db-password:
    file: db/password.txt

다음 명령을 실행해 Compose Watch로 어플리케이션을 실행해요.

$ docker compose watch

터미널에서 어플리케이션을 curl로 호출해 응답을 받아요.

$ curl http://localhost:8000
Hello, Docker!

로컬 머신의 어플리케이션 소스 파일에 가한 어떤 변경이든, 이제 실행 중인 컨테이너에 즉시 반영돼요.

python-docker-example/app.py를 IDE나 텍스트 편집기로 열고 Hello, Docker! 문자열에 느낌표를 몇 개 더 추가해요.

-    return 'Hello, Docker!'
+    return 'Hello, Docker!!!'

app.py의 변경 사항을 저장하고 몇 초 동안 어플리케이션이 다시 빌드될 때까지 기다려요. 어플리케이션을 다시 curl로 호출하고 업데이트된 텍스트가 나타나는지 확인해요.

$ curl http://localhost:8000
Hello, Docker!!!

터미널에서 ctrl+c를 눌러 어플리케이션을 중지해요.

Python을 위한 린팅, 포맷팅, 타입 검사 (Linting, formatting, and type checking for Python)

준비 사항 (Prerequisites)

앱 개발을 완료해요. 이 주제는 로컬 Python 설치가 필요해요. 여기서 소개하는 도구와 Git 훅이 호스트에서 실행되기 때문이에요. Python을 로컬에 설치하고 싶지 않다면 이 주제를 건너뛰어도 돼요. 같은 검사들을 CI에서 실행할 수도 있어요.

개요 (Overview)

린팅, 포맷팅, 타입 검사는 코드가 실행되기 전에 버그를 잡고, 스타일을 강제하며, 타입 오류를 발견하는 자동화된 방법이에요. 매 커밋마다, CI에서, 그리고 편집기에서 실행하면 수정 비용이 싼 초기에 문제를 잡을 수 있어요.

이 섹션에서는 Python 어플리케이션용 세 가지 도구를 구성할 거예요. Ruff는 한 번의 빠른 패스에서 린팅과 포맷팅을 처리해요. Pyright는 코드에서 타입 오류를 정적으로 검사해요. Pre-commit 훅은 각 Git 커밋 전에 이 두 가지를 자동으로 실행해서, 문제가 커밋되기 전에 로컬에서 잡히도록 해요.

Ruff로 린팅과 포맷팅하기 (Linting and formatting with Ruff)

Ruff는 Rust로 작성된 매우 빠른 Python 린터이자 포맷터예요. flake8, isort, black 같은 여러 도구를 단일 통합 도구로 대체해요.

python-docker-example 디렉토리에 pyproject.toml 파일을 만들어요:

# Configuration for code-quality tools.
# - [tool.ruff]: linting and formatting (https://docs.astral.sh/ruff/)
# - [tool.pyright]: static type checking (https://microsoft.github.io/pyright/)

[tool.ruff]
target-version = "py312"

[tool.ruff.lint]
select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "B",  # flake8-bugbear
    "C4",  # flake8-comprehensions
    "UP",  # pyupgrade
    "ARG001", # unused arguments in functions
]
ignore = [
    "E501",  # line too long, handled by black
    "B008",  # do not perform function calls in argument defaults
    "W191",  # indentation contains tabs
    "B904",  # Allow raising exceptions without from e, for HTTPException
]

Ruff를 설치해요:

$ pip install ruff

가상 환경을 사용한다면 ruff 명령을 사용할 수 있도록 활성화되어 있는지 확인해요.

이 명령들로 코드를 검사하고 포맷해요:

# Check for errors
$ ruff check .

# Automatically fix fixable errors
$ ruff check --fix .

# Format code
$ ruff format .

Pyright로 타입 검사하기 (Type checking with Pyright)

Pyright는 현대 Python 기능과 잘 동작하는 빠른 Python 정적 타입 검사기예요.

pyproject.toml을 업데이트해 맨 아래에 Pyright 구성을 추가해요.

# Configuration for code-quality tools.
# - [tool.ruff]: linting and formatting (https://docs.astral.sh/ruff/)
# - [tool.pyright]: static type checking (https://microsoft.github.io/pyright/)

[tool.ruff]
target-version = "py312"

[tool.ruff.lint]
select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "B",  # flake8-bugbear
    "C4",  # flake8-comprehensions
    "UP",  # pyupgrade
    "ARG001", # unused arguments in functions
]
ignore = [
    "E501",  # line too long, handled by black
    "B008",  # do not perform function calls in argument defaults
    "W191",  # indentation contains tabs
    "B904",  # Allow raising exceptions without from e, for HTTPException
]

[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.12"
exclude = [".venv"]

Pyright를 설치하고 실행해요:

$ pip install pyright
$ pyright

Pre-commit 훅 설정하기 (Setting up pre-commit hooks)

Pre-commit 훅은 각 커밋 전에 로컬 머신에서 자동으로 검사를 실행해요. Ruff 훅을 설정하려면 python-docker-example 디렉토리에 .pre-commit-config.yaml 파일을 만들어요:

# Pre-commit hook configuration. Runs Ruff (lint + format) on every
# `git commit`. See https://pre-commit.com/

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.15
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

설치하고 사용하려면:

$ pip install pre-commit
$ pre-commit install
$ git commit -m "Test commit"  # Automatically runs checks

요약 (Summary)

이 섹션에서 다음을 배웠어요:

  • 린팅과 포맷팅에 Ruff 구성·사용하기
  • 정적 타입 검사에 Pyright 설정하기
  • pre-commit 훅으로 검사 자동화하기

이 도구들은 코드 품질을 유지하고 개발 초기에 오류를 잡는 데 도움을 줘요.

관련 정보:

다음 단계 (Next steps)

  • 팀의 스타일 선호에 맞게 린팅 규칙 커스터마이즈하기
  • 고급 타입 검사 기능 탐구하기

더 알아보기 (Learn more)