![]()
Ruff 0.15.9에서 ‘조용히’ 바뀐 것들: 린트/포맷 업그레이드 안전하게 하는 체크리스트
meta_description: Ruff 0.15.9(2026-04-02)는 큰 기능 추가보다 규칙/수정 안전성/포맷 옵션 같은 ‘업그레이드 때 사고 나는 지점’을 건드린 릴리스다. 이 글은 실제 프로젝트에서 ruff를 올릴 때 바로 쓰는 20분 체크리스트(잠금/preview 분리/unsafe fix 관리/노트북 W391/새 포맷 옵션)와 CI 적용 팁을 정리한다. meta_keywords: python,ruff,lint,formatter,format,pyflakes,pyupgrade,pycodestyle,preview,unsafe fix,CI,pre-commit,notebook,W391,F811,RUF010,nested-string-quote-style,UP012,UP018,SIM105 meta_robots: index,follow
Ruff 업그레이드는 대개 “버전만 올리고 끝”이 아니다.
특히 팀/CI가 붙어 있는 프로젝트에서는, 릴리스 노트의 한 줄이 곧바로 빨간불(빌드 실패)로 바뀐다.
이번 Ruff 0.15.9(2026-04-02)는 겉으로는 조용한 패치 릴리스처럼 보이는데, 실제로는 업그레이드 때 사고 나는 포인트를 몇 군데 건드렸다.
- (preview) 타입 힌트가 붙은 변수 재선언을
F811로 잡기 시작 - fix의 안전성(unsafe) 표기 강화 (
RUF010) - 노트북 셀에서의
W391fix 동작 조정 - 포매터 옵션 추가:
nested-string-quote-style
이 글은 “릴리스 노트 요약”이 아니라, 프로젝트에서 안전하게 올리는 순서로 정리한다.
![]()
1) 먼저 결론: 0.15.9 업그레이드는 ‘preview 분리’가 핵심
0.15.9에서 가장 실무적으로 체감 큰 변화는 “preview 모드에서 규칙이 조금 더 까다로워질 수 있다”는 점이다.
대표적으로 릴리스 노트에 있는 이 항목:
- preview 모드에서, 타입 애너테이션이 있는 변수 재선언을
F811로 플래그 (pyflakes)
즉, 다음과 같은 코드가 팀에 많으면 갑자기 잡힐 수 있다.
x: int = 1
x: int = 2 # preview에서 F811로 잡힐 수 있음
실전 팁:
- ruff를 “기본”과 “preview”로 나눠서 운영하면 업그레이드 스트레스가 크게 줄어든다.
2) 20분 업그레이드 체크리스트(복붙)
나는 ruff 업그레이드를 할 때 아래 순서로만 본다.
(1) 버전 잠금부터 확인
pyproject.toml또는 lockfile에서 ruff 버전이 고정돼 있나?- CI와 로컬 버전이 섞이면, 같은 PR에서 결과가 달라진다.
예시(의도적으로 고정):
[tool.ruff]
required-version = "==0.15.9"
(2) --fix는 바로 켜지 말기
0.15.9에서 RUF010처럼 “fix가 unsafe인 조건”이 더 명확해졌다.
실전에서는 아래처럼 단계가 안전하다.
1) 먼저 린트만 돌려서 새 경고가 무엇인지 확인
2) 그 다음 --fix는 CI에서 허용 범위를 정하고 실행
(3) 노트북(Jupyter) 있는 repo는 W391만 따로 보기
릴리스 노트에 노트북 셀 관련 W391 fix 동작이 언급된다.
노트북이 섞인 repo에서는, 포매터/린터가 “의도치 않게 셀 경계를 건드리는지”가 체감 이슈다.
- 노트북을 코드 리뷰 대상에 넣는 팀이면 특히
(4) 포매터 옵션 추가는 ‘팀 규칙’으로 합의 후 적용
0.15.9에서 포매터 옵션 nested-string-quote-style이 추가됐다.
이건 한 번 적용하면 diff가 꽤 날 수 있다.
그래서 아래 원칙을 추천한다.
- 업그레이드 PR과 스타일 변경 PR을 분리
- 스타일 옵션은 “적용 날짜/버전”을 PR 본문에 적어두기
3) CI에서 깨지는 걸 줄이는 운영 패턴 3개
패턴 A) 린트와 포맷을 분리
ruff check .
ruff format --check .
포맷이 한 번 깨지기 시작하면, 린트와 섞여서 디버깅이 어려워진다.
패턴 B) preview는 별도 job으로
- 기본 job:
preview = false - 실험 job:
preview = true+ 실패해도 전체 파이프라인은 막지 않기(팀 성격에 따라)
preview는 “미래의 기본값”을 미리 보게 해주지만, 당장 모든 PR을 막아버리면 팀이 금방 피로해진다.
패턴 C) unsafe fix는 의도적으로 리뷰에 남기기
ruff가 unsafe로 표시하는 수정은, 대부분 “의미가 바뀔 수 있는” 타입이다.
그래서 나는 unsafe fix는 자동 적용보다,
- CI에서는 리포트만 생성
- 개발자가 로컬에서 적용하고 diff를 리뷰로 남김
이 흐름을 선호한다.
4) 0.15.9 릴리스 노트에서 실무적으로 눈여겨볼 항목들
여기부터는 “내가 PR에서 실제로 체크하는 포인트”만 뽑는다.
(preview) F811 관련: 타입 애너테이션 변수 재선언
- 코드베이스에 데이터 로딩/설정 코드가 많으면 종종 나온다.
- 특히 테스트에서 임시로 덮어쓰기 하는 패턴
대응은 두 가지:
- 진짜 실수면: 재선언 대신 다른 이름
- 의도면: 해당 구간만
# noqa: F811같은 식으로 명시(팀 룰에 따라)
(rule change) SIM105와 except* (3.12 이전)
except*는 파이썬 3.11+에서 생긴 문법인데, 팀의 최소 버전에 따라 룰 적용이 달라지는 지점이다.
- 멀티 버전 지원 프로젝트면 “최소 버전”을 설정 파일에 명시해두는 게 낫다.
(formatter) nested-string-quote-style
여기서 중요한 건 기능 자체보다 운영이다.
- 옵션을 켜는 순간 PR diff가 커질 수 있다.
- 스타일 PR은 기능 PR과 섞으면 리뷰가 망한다.
5) pyproject.toml 최소 설정 예시(기본/preview 분리)
0.15.9에서 새로 생긴 룰이나 변경된 룰 때문에 갑자기 터질 때, 대부분은 설정이 ‘한 덩어리’라서 수습이 어려워진다.
내가 자주 쓰는 방식은 기본 설정은 최대한 보수적으로 두고, preview는 별도 프로파일처럼 운영하는 것이다.
[tool.ruff]
required-version = "==0.15.9"
# 팀 최소 지원 버전이 있다면(중요)
target-version = "py311"
[tool.ruff.lint]
# 기본은 안정적인 룰만
select = ["E", "F", "I"]
# 자동 수정은 신중하게
fixable = ["ALL"]
unfixable = []
[tool.ruff.format]
# 포맷 옵션은 업그레이드 PR과 분리해서 켜는 걸 추천
# nested-string-quote-style = "double"
preview를 쓰고 싶다면, 나는 보통 “CI의 별도 job”에서만 켠다.
(한 파일 안에서 프로파일을 완벽히 나누기는 어렵지만, 실행 커맨드/CI 분리만으로도 효과가 크다.)
6) pre-commit과 CI를 같이 맞추는 샘플(여기서 실수가 제일 많이 난다)
로컬에서는 통과하는데 CI에서만 실패하는 가장 흔한 이유는, pre-commit과 CI가 서로 다른 커맨드를 돌리기 때문이다.
pre-commit을 쓴다면 최소한 아래 두 개는 통일하는 게 좋다.
# .pre-commit-config.yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.9
hooks:
- id: ruff
args: ["check", "."]
- id: ruff-format
args: ["format", "--check", "."]
CI도 같은 두 줄로 끝내는 게 가장 안전하다.
ruff check .
ruff format --check .
여기서 포인트 하나.
- 업그레이드 PR에서는
--fix를 CI에 넣지 말자. - 포맷은
--check로만 두고, 실제 적용은 개발자가 로컬에서 실행해서 diff를 남기는 게 리뷰 품질이 좋다.
7) 업그레이드 후 “갑자기 많이 뜨는 경고”를 다루는 방식
업그레이드 직후 경고가 쏟아지면, 사람들은 두 가지 극단으로 간다.
- (A) 다 꺼버린다(린터를 버림)
- (B) 다 고치느라 기능 개발이 멈춘다
나는 (C)로 간다.
- 당장 중요한 것(버그 가능성/명백한 코드 스멜)만 남기고
- 스타일/선호는 별도 PR로 분리
실전에서는 이런 규칙이 잘 먹힌다.
1) 새로 생긴 경고는 “신규 코드부터” 적용 - 기존 코드 베이스는 baseline으로 두고, 신규/변경 파일 위주로 먼저 정리
2) unsafe fix는 강제하지 않는다
- RUF010처럼 fix 안전성이 걸리는 건, 자동 적용보다 리뷰가 낫다
3) 노트북 관련 이슈는 노트북 팀 룰로
- W391 같은 건 파이썬 코드보다 노트북 워크플로에 더 민감하다
이렇게 하면 업그레이드가 “한 번에 끝내는 작업”이 아니라, 2~3개의 작은 PR로 나뉘어 현실적으로 진행된다.
8) 업그레이드 PR 템플릿(실제로 이렇게 올리면 편하다)
PR 제목 예시:
chore: bump ruff to 0.15.9
PR 본문 체크 박스:
- [ ]
required-version고정 - [ ]
ruff check .통과 - [ ]
ruff format --check .통과 - [ ] preview job 결과 확인(별도 링크)
- [ ] unsafe fix는 자동 적용하지 않음(또는 적용했다면 근거/스크린샷)
이렇게 해두면, 다음 업그레이드 때 “왜 이렇게 했지?”를 다시 찾지 않아도 된다.
![]()
9) nested-string-quote-style 옵션은 ‘스타일 PR’로 따로 빼자
0.15.9에서 추가된 nested-string-quote-style은 팀에 따라 꽤 유용할 수 있다.
하지만 이런 류의 옵션은 보통 “코드 의미는 그대로인데 diff가 커지는” 스타일 변경을 만든다.
그래서 나는 항상 규칙을 하나 둔다.
- 업그레이드 PR: 도구 버전만 올리고, 기존 스타일은 유지
- 스타일 PR: 포맷 옵션을 켜고, 리뷰는 기계적 변경으로 간주
예를 들어 문자열 중첩 인용부호 스타일을 바꾸면, 아래 같은 변화가 생길 수 있다.
# 예시(의도만 보여주는 코드)
msg = f"He said, 'hello'"
이런 변화가 수백 파일에서 동시에 터지면, 실제 버그 수정 PR이 묻힌다.
요약하면:
- 옵션은 좋다
- 하지만 ‘타이밍’이 더 중요하다
Sources
- Ruff GitHub Releases — 0.15.9 (Released 2026-04-02)
- https://github.com/astral-sh/ruff/releases/tag/0.15.9
- PyPI — ruff 0.15.9 (release metadata)
- https://pypi.org/project/ruff/0.15.9/
Keywords
python,ruff,lint,formatter,format,pyflakes,pyupgrade,pycodestyle,preview,unsafe fix,CI,pre-commit,notebook,W391,F811,RUF010,nested-string-quote-style,UP012,UP018,SIM105
이미지 크레딧/라이선스
- Laptop coding programs (Unsplash).jpg — Tirza van Dijk / CC0
- https://commons.wikimedia.org/wiki/File:Laptop_coding_programs_(Unsplash).jpg
- http://creativecommons.org/publicdomain/zero/1.0/deed.en
- CSS code on a screen (Unsplash).jpg — Sai Kiran Anagani / CC0
- https://commons.wikimedia.org/wiki/File:CSS_code_on_a_screen_(Unsplash).jpg
- http://creativecommons.org/publicdomain/zero/1.0/deed.en
댓글
댓글 쓰기