Skip to content

[RFC] The way to update Python standard library #5974

@moreal

Description

@moreal

Preface

This RFC was first opened on July 15, 2025, but was updated on January 19, 2026. If you are curious about the original content, it has been preserved at the bottom within a <details> tag for you to expand and read. Additionally, GitHub allows you to view the full history of changes, so you may use that feature as well.

Background

With each new Python version release, the standard libraries under Lib/ are also updated accordingly. New features are added, and bugs are fixed.

On the RustPython side, we update the standard libraries to keep pace with each Python version upgrade. We strive to follow along as closely as possible—temporarily skipping failing tests or adding the @unittest.expectedFailure decorator to allow failures—and work toward gradual improvements.

While this sounds straightforward, it is largely repetitive work. That is why this issue was opened last year, and thanks to many contributors around that time, we developed AST-based scripts such as scripts/lib_updater.py1 and scripts/auto_mark_test.py2. These have since been consolidated into a single toolchain called scripts/update_lib3. Furthermore, by utilizing coding agents like Claude Code, we have automated a significant portion of what was previously done manually.4

Proposal

However, I believe there is still considerable room for further automation. When a new Python version is released or when needed, we should be able to easily update the libraries under the Lib/ directory. By doing so, we can then focus our efforts on improving RustPython's performance or adding support for currently unsupported native features (e.g., zstd). With this in mind, I would like to share a few proposals (ideas). These are not detailed specifications; detailed specifications should be created as separate issues.

Improving the Module Update Review Process

While various automation efforts have been made, the use of Claude Code in particular has enabled a significant portion of the work to be done automatically, making code review increasingly important. The ability to make numerous changes in a short time has resulted in a substantially larger volume of content to review—both for the person doing the work and for reviewers. Of course, one approach is to trust that Claude Code and similar tools have done their job well, but this is not ideal, so we need methods for verification.

Personally, I have been performing the following verification steps:

  • First, I use git diff to scan through and check whether previously added modifications such as XXX: RUSTPYTHON have been preserved. If they have been removed, there may be issues in testing, but not necessarily, so I verify manually.
  • Then, to check whether modules have been copied correctly, I run commands like delta Lib/<lib>.py cpython/Lib/<lib>.py and delta Lib/test/<lib>.py cpython/Lib/test/<lib>.py, or delta Lib/<lib>/ cpython/Lib/<lib>/ and delta Lib/test/test_<lib>/ cpython/Lib/test/test_<lib>/, and confirm that the only remaining differences are sections like TODO: RUSTPYTHON and @unittest.expectedFailure. If so, I can confirm that the module was copied correctly and there were no unintended deletions.

While the process has become much less cumbersome since we no longer need to add @unittest.expectedFailure as before, I believe it can be automated further.

Updating standard libraries can be viewed as roughly three stages, as done in our scripts:

  1. Delete the relevant library code and copy the code from the CPython repository (i.e., the cpython/ directory).
  2. Run scripts/update_lib to transform failing tests by adding @unittest.expectedFailure or @unittest.skip.
  3. Restore any manually added sections such as XXX: RUSTPYTHON.

The reason for dividing it into these three stages is that only stage 3 actually requires direct human review. Stage 1 should be automated and verified to ensure that CPython was copied correctly without human verification. Stage 2 should be verified by running the script on the results of stage 1 and confirming that the same results are produced. Only the additional modifications in stage 3 require human judgment. This approach would significantly reduce the burden by eliminating the need to review changes to the standard library itself.

To achieve this, the primary approach would be to separate commits by stage, providing convenience during review.
By having CI such as GitHub Actions automatically verify commits 1 and 2, human reviewers would only need to focus on carefully reviewing commit 3, without needing to verify commits 1 and 2.

Alternatively, another approach could be to create the content for stage 3 as a separate file like Lib/test/test_something.py.patch and implement a separate unittest.TestLoader that applies the patch file when loading tests. However, this is not something I am strongly advocating for in this proposal.

Providing GitHub Action for Module Updates

During the module update process, verification is needed to ensure everything works correctly. From an individual contributor's perspective, it is difficult to know how things will behave across Ubuntu, Windows, and macOS. If a function is simply not supported on Windows, there is no additional work required, but if a test fails due to RustPython-specific issues, this needs to be managed separately.

In the current situation, contributors run tests on their personal work environment's OS, submit a PR, and only then discover if additional failures occur. However, in environments like GitHub Actions where tests can be run across multiple platforms, this would not be an issue.

The goal of this item is to have some mechanism (e.g., GitHub Actions workflow dispatch, GitHub bot, Discord bot, etc.) that, when triggered, runs tests on supported platforms such as Ubuntu, Windows, and macOS, aggregates the test results, adds the appropriate decorators, and creates a draft PR. While not certain, it may also be possible to automate stage 3 from the above proposal by utilizing Copilot.

Currently, scripts/update_lib can extract patch information using the patches subcommand, so after running on each platform, the results can be temporarily uploaded to something like GitHub Actions Artifacts, retrieved in a subsequent job, merged, and then applied.

Module Dependency Graph Inspection Tool

Currently, we update a standard library (e.g., json) and its corresponding tests (e.g., test_json), run the tests, and add or remove decorators accordingly. However, since changes affect other places that use that library, we need to run the full test suite. Running the full test suite is, of course, a resource-intensive operation. We need to identify libraries and tests that depend on the library being updated by analyzing import, __import__, etc., create a dependency graph, and determine which libraries must be tested. This is likely a similar concept to what is done in monorepos.

Related PRs:


한국어 원본

서문

이 RFC는 2025년 7월 15일에 처음 열렸으나 2026년 1월 19일에 업데이트 되었습니다. 만약 원래 내용이 궁금하시다면 최하단에 <details> 태그로 보존해 놓았으므로 열어 읽어보실수 있습니다. 또한 GitHub는 내용 변경 기록을 모두 볼 수 있으므로 그 기능을 사용하셔도 됩니다.

배경

Python 버전이 올라갈 마다 Lib/ 아래에 있는 표준 라이브러리들 또한 함께 업데이트 됩니다. 기능이 추가되기도 하고 버그가 고쳐지기도 합니다.

RustPython 측에서는 Python 버전이 올라갈 때마다 따라 표준 라이브러리들을 업데이트 합니다. 최대한 따라가면서 실패하는 테스트는 임시로 스킵하거나 @unittest.expectedFailure 데코레이터를 붙여 실패를 허용하고, 점진적으로 개선하는 방향으로 작업하고 있습니다.

말로는 간단하지만 이는 단순 반복 작업에 가깝습니다. 그래서 작년에 이 이슈를 열게 되었고 그 부근에 많은 기여자분들 덕분에 scripts/lib_updater.py1scripts/auto_mark_test.py2 같은 AST 기반 스크립트도 있었고 현재는 scripts/update_lib 라는 하나의 툴체인으로 통합되었습니다3. 그리고 Claude Code 같은 코딩 에이전트를 이용하여 과거 수동으로 작업하던 것들을 상당 부분 자동화하였습니다.4

제안

하지만 아직 더 자동화 할 여지가 많다고 생각합니다. 파이썬에서 새로운 버전이 나오게 되거나 필요할 때 손쉽게 Lib/ 디렉터리 아래 라이브러리들을 업데이트할 수 있어야 합니다. 그러고 나서 RustPython 성능 개선이나 미지원 중인 네이티브 기능(e.g., zstd)을 추가로 지원하는데 힘 쓸 수 있기를 기대하며 몇 가지 제안(아이디어)을 남겨봅니다. 상세한 스펙까지는 아니며 상세한 스펙은 별도 이슈로 만들어야 합니다.

모듈 업데이트 리뷰 과정 개선

여러 자동화들이 있었지만 특히나 Claude Code를 사용하면서 상당 부분 자동으로 진행해주게 되면서 코드를 리뷰하는 것은 굉장히 중요해졌습니다. 짧은 시간에 많은 변경을 할 수 있게 되면서 작업자 스스로는 물론이고 리뷰어 입장에서도 리뷰해야할 양이 굉장히 커졌습니다. 물론 Claude Code 등이 잘 했으리라 믿는 것도 방법이지만 좋은 방법은 아니므로 검증할 방법이 필요합니다.

개인적으로는 아래와 검증을 했습니다:

  • 우선 git diff로 XXX: RUSTPYTHON 같이 기존에도 추가적인 수정을 더했던 부분들이 보존되어 있는지 훑어봅니다. 만약 사라졌다면 테스트에서도 문제가 생길 여지가 있지만 그렇지 않을수도 있으므로 수동으로 확인합니다.
  • 그리고 모듈이 잘 복사 되었는지 보기 위해 delta Lib/<lib>.py cpython/Lib/<lib>.pydelta Lib/test/<lib>.py cpython/Lib/test/<lib>.py, 혹은 delta Lib/<lib>/ cpython/Lib/<lib>/delta Lib/test/test_<lib>/ cpython/Lib/test/test_<lib>/ 같은 명령어를 실행하고 차이점이 TODO: RUSTPYTHON, @unittest.expectedFailure 같은 부분만 남아 있는지 확인했습니다. 만약 그렇다면 모듈이 잘 복사되었고 의도치 않은 삭제가 없었다고 알 수 있습니다.

이전과 같이 @unittest.expectedFailure를 달 필요가 없으므로 훨씬 덜 번거로워졌지만 좀 더 자동화 할 수 있을 것 같습니다.

표준 라이브러리를 업데이트 하는 일은 스크립트에서 하듯이 크게 3단계로 볼 수 있습니다.

  1. 해당 라이브러리 관련 코드를 지우고 CPython 저장소(i.e., cpython/ directory)로 부터 코드를 복사합니다.
  2. scripts/update_lib 를 실행하여 실패하는 테스트들에 코드를 변형하여 @unittest.expectedFailure@unittest.skip를 붙입니다.
  3. XXX: RUSTPYTHON 등과 같이 임의로 추가되었던 부분들을 복구합니다.

이렇게 3단계로 나눈 이유는 실질적으로 직접 리뷰해야할 부분은 3번 뿐이기 때문입니다. 1번 단계는 사람이 검증하지 않고 자동화하여 CPython을 잘 복사하였는지 검증해야 하고, 2번 단계는 1번 단계의 결과에서 스크립트를 실행하여 같은 결과가 나오는지를 검증해야 합니다. 그리고 3번 단계에서 추가로 변형된 부분만 사람이 판단해야 합니다. 그러면 표준 라이브러리의 변경 사항을 확인하지 않아도 되니 훨씬 부담이 줄어들 것이라 생각합니다.

이를 달성하기 위해 1차적으로는 각 단계별로 커밋을 분리함으로써 리뷰할 때 편리함을 얻을 수 있습니다.
GitHub Actions 같은 CI에서 1번, 2번 커밋을 자동으로 검증함으로써 사람 리뷰어에게는 1번, 2번 커밋을 검증하지 않아도 되도록 하고 3번 커밋만 잘 검토하는 것으로 작업이 끝나도록 합니다.

혹은 다른 방법으로 3번 커밋에 대한 내용을 Lib/test/test_something.py.patch 같은 별도 파일로 만들고 별도의 unittest.TestLoader 를 구현하여 테스트를 불러올 때 패치 파일을 적용하도록 하는 방법도 있을 것입니다. 하지만 이 제안에서 강하게 주장하는 부분은 아닙니다.

모듈 업데이트 용 GitHub Actions 제공

모듈을 업데이트 하는 과정에서 제대로 동작하는지 확인이 필요합니다. 개인의 입장에서는 ubuntu, windows, macOS 별로 어떻게 동작할지 알기 어렵습니다. 아예 Windows에서는 지원하지 않는 함수일 경우 추가적으로 할 작업이 없지만 RustPython로 인해 실패하는 테스트일 경우 이는 추가적으로 관리해야 합니다.

현재 상황에서는 개인 작업 환경의 OS에서 테스트를 돌려보고 PR을 올린뒤 이후 추가적으로 깨지는지 알게 되는데 GitHub Actions 같이 여러 플랫폼에서 실행해볼수 있는 환경에서는 이럴일이 없습니다.

이 항목에서 목표는 모종의 수단 (e.g., GitHub Actions workflow dispatch, GitHub bot, Discord bot, etc..) 를 사용하면 트리거 되면 Ubuntu, Windows, macOS 같은 지원하는 플랫폼에서 테스트를 실행하고 테스트의 실행 결과를 집계하여 데코레이터를 달고 PR 초벌까지 만들어주는 것입니다. 확실치 않지만 Copilot를 활용하여 이후 위 제안의 3번 단계도 자동화 할 수도 있을 것입니다.

현재 scripts/update_lib에서 patches 서브 커맨드로 패치 내역을 추출해낼 수 있으므로 플랫폼 별로 실행 후 GitHub Actions의 경우 Artifact 같은 곳에 임시로 업로드하고 다음 Job에서 가져와 이를 통합하고 실제로 적용하면 됩니다.

모듈 간 의존성 그래프 확인 도구

지금은 기본적으로 표준 라이브러리(e.g., json)를 업데이트하고 그에 상응하는 테스트(e.g., test_json)를 업데이트 및 실행해보고 데코레이터를 추가 및 삭제합니다. 하지만 해당 라이브러리를 사용하는 다른 곳에도 영향을 가기 때문에 전체 테스트를 돌려봐야 하는 문제가 있습니다. 하지만 전체 테스트를 돌려보는 것은 당연히 리소스를 많이 소모하는 작업입니다. 업데이트 하는 라이브러리에 의존하는 라이브러리 및 테스트들을 import, __import__ 등으로 파악하여 의존성 그래프를 만들고 꼭 테스트 해야하는 라이브러리들을 알아내야 합니다. 이는 아마 monorepo에서 하는 것과 비슷한 개념일 것입니다.


당연한 이야기지만 제가 놓친 부분이 있거나 다른 의견이 있다면 편하게 남겨주세요!

Revision at July 15, 2025

Preface

Recently, while attempting to automate the process of updating Python standard libraries in RustPython, I became exhausted from various challenges and stopped working on it. However, I suddenly wanted to preserve these ideas, so I'm writing this post.
The problems and ideas I considered might not actually be very helpful, so it's fine if they're just treated as "Issue A" and moved on.
Since I wrote this in my native Korean before having it translated, there may be some mistranslations, but I ask for your understanding.

Problems

Regarding Python standard library updates, I see two main issues. One is that updating them requires significant labor, and the other is that it's difficult to easily determine which Python version each file is based on.

From a labor perspective

I'm not sure how others work, but in my case, I would copy files from the CPython repository to the same path in the RustPython repository, then use diff commands to try preserving existing comments.
Manually adding decorators like @unittest.expectedFailure was quite labor-intensive.

As a solution, I considered automated scripts running on GitHub Actions. I thought about implementing it with the following structure, but couldn't proceed due to difficulties in setting up different OS hosts and personal exhaustion:

  1. Copy desired modules or files from the CPython repository.
  2. Run the following process in Windows, macOS, and Linux environments to collect reports:
    1. Use cargo run -- -m test --list-cases <test> to get all potentially executable test cases.
    2. Run each test case in a separate process with cargo run -- -m unittest <test>, as some cases might cause panics or deadlocks.
    3. Collect test success/failure/panic status and create a JSON report to upload to a separate repository (e.g., S3).
  3. Use LibCST to parse test files and build an inheritance graph between test classes.
  4. Organize failing or panicking test cases from reports and create an execution plan, considering whether to add decorators to parent classes or create arbitrary methods in child classes that call parent methods.
  5. Apply changes using LibCST according to the execution plan and submit a PR.
  6. Have humans review and finally merge it.

This approach somewhat ignores:

  • Existing comments are disregarded. For example, failure reasons appended to TODO: RUSTPYTHON comments aren't preserved. They could be preserved, but I couldn't establish criteria for which comments to keep.

While agents like Claude Code can handle most cases well, making such automation somewhat less meaningful, I still believe automating what can be automated is good.
If this idea seems reasonable and there are edge cases I haven't considered, please add comments with additional opinions.

About standard library version management

This idea actually came up while writing the automation script.
Currently, the RustPython project takes standard library files from CPython, arbitrarily adds decorators like @unittest.expectedFailure to pass tests, then gradually improves them.
However, since we modify the original standard library files, it's hard to immediately tell which version they're based on when comparing with CPython standard library files (though you can check commit logs), and we need to use libraries like LibCST for somewhat complex modifications.

This idea aims to use original files unchanged to make updates easier and simplify version identification (e.g., whether it's a CPython a.b version file).

Unlike the previous idea, I don't have concrete implementation details, but the concept would be:

  • Create a custom test runner inheriting from unittest test runner and use it as the main runner. This runner would receive lists of which test cases fail or should be skipped.
  • The test list would be an array like <testcase>: FAILURE | SKIP. Based on values, it would skip or run expecting failure. Managed per OS (e.g., testhints/linux-arm64.json).

However, the current approach of adding decorators and comments allows searching for TODO: RUSTPYTHON to find work targets while viewing test source code, so changing to this new idea might lose this advantage.
Also, since I've never actually attempted to implement this idea, it might be somewhat flawed.

(Korean Original)

서문

얼마전에 RustPython에 Python 표준 라이브러리들을 업데이트 하는 방식을 자동화해보려다가 여러 고민에 지쳐 더 작업을 안 하고 있지만 문득 아이디어들은 남겨놓고 싶어서 이 글을 적습니다.
제가 생각했던 문제 및 아이디어가 사실 크게 도움이 되지 않는 것일수도 있기 때문에 그냥 "이슈 A"로 취급되고 넘겨져도 좋다고 생각합니다.
제가 익숙한 한국어로 작성한 뒤 번역되어 올라가기 때문에 다소 오역될 수 있겠습니다만, 양해 부탁드립니다.

문제점

제가 생각하는 Python 표준 라이브러리 업데이트와 관련한 문제는 두가지 시각이 있습니다. 하나는 이를 업데이트하는데 노동력이 많이 필요하다는 점이고, 하나는 각각의 파일들이 현재 Python 몇 버전 기준 파일인지 쉽게 알기 어렵다는 점입니다.

노동력의 측면에서

다른 분들이 어떻게 작업하시는지 잘 모르겠지만 제 경우에는 CPython 저장소에 있는 파일을 RustPython 저장소의 같은 경로에 복사한 뒤 diff 명령어를 활용하여 기존에 있던 주석들을 그대로 보존하려고 노력하기도 했습니다.
그리고 @unittest.expectedFailure 같은 데코레이터를 손수다는 것은 꽤 노동력을 많이 들이는 작업이었습니다.

이것을 해결하는 방법으로 GitHub Actions 등에서 도는 자동화된 스크립트를 생각했습니다. 아래와 같은 구조로 작성해보리라 생각했지만 각각의 운영체제 호스트를 어떻게 구축할 지에 대한 어려움과 개인적인 체력 부족으로 인해 진행하지 못 했습니다.

  1. CPython 저장소에서 원하는 모듈 혹은 파일을 복사해옵니다.
  2. 아래의 과정을 Windows, macOS, Linux 같은 환경에서 각각 돌려 보고서를 수집합니다.
    1. cargo run -- -m test --list-cases <test> 명령어로 실행될 여지가 있는 테스트 케이스를 모두 가져옵니다.
    2. cargo run -- -m unittest <test> 명령어로 각각의 테스트 케이스를 별도의 프로세스로 실행합니다. 이는 어떤 테스트 케이스는 패닉을 유발할 수도 있고, 데드락이 걸려 멈추지 않을수도 있기 때문입니다.
    3. 이 테스트 성공, 실패, 패닉 여부를 모아 JSON 형식의 보고서로 만들고 별도의 저장소(e.g., S3 등)에 올립니다.
  3. LibCST로 테스트 파일을 파싱하여 테스트 클래스 간의 상속 관계 그래프를 구성합니다.
  4. 보고서로 부터 실패 혹은 패닉이 발생하는 테스트 케이스들을 모아 정리하고, 위에서 구성한 상속 관계 그래프를 참조하여 부모 클래스에 데코레이터를 달지, 자식 클래스에 부모 메소드를 호출하는 임의의 메소드를 생성하고 데코레이터를 달지 등 실행 계획을 작성합니다.
  5. 작성된 실행 계획에 따라 LibCST로 실제로 데코레이터를 다는 등의 변경을 가하여 PR을 올립니다.
  6. 사람이 이를 리뷰하고 최종적으로 머지합니다.

위 방식은 아래와 같은 것을 다소 무시합니다:

  • 기존에 갖고 있던 주석은 무시됩니다. 예를 들어 TODO: RUSTPYTHON 주석에 덧붙여 있던 실패 사유는 보존되지 않습니다. 잘 보존할 수도 있지만 주석들 중 어떤 주석을 보존하여 가져가야 하는지 기준을 세우지 못 했습니다.

Claude Code 같은 에이전트가 대부분의 경우 알아서 잘 해주므로 이런 자동화의 의미가 다소 퇴색될 수 있으나 그래도 자동으로 할 수 있는 것은 자동으로 하는 것이 좋다고 생각합니다.
만약 이 아이디어가 괜찮아 보이고 제가 생각하지 못 한 엣지케이스가 있다면 코멘트로 추가로 의견을 달아주세요.

표준 라이브러리 버전 관리에 대하여

이 아이디어는 사실 위 자동화 스크립트를 작성하다가 든 생각 중 하나입니다.
현재 RustPython 프로젝트는 CPython 저장소에 있는 표준 라이브러리 파일을 가져와 @unittest.expectedFailure 같은 데코레이터를 임의로 달아 테스트를 통과할 수 있게 한 뒤 점진적으로 개선해나가는 방향을 취하고 있습니다.
하지만 일단 원본 표준 라이브러리 파일에 변경을 가하므로 CPython 표준 라이브러리 파일과 비교하는 식으로 비교할 때 어떤 버전의 파일인지 바로 알기 어렵고 (커밋 로그를 확인하면 됩니다만), LibCST 같은 라이브러리를 활용하여 다소 복잡한 수정을 가해야 한다는 점입니다.

이 아이디어는 원본 파일을 변경없이 사용하여 파일 업데이트을 쉽게 하고, CPython a.b 버전 파일인지 등의 확인을 쉽게 만드는 것이 목적입니다.

앞선 아이디어처럼 구체적인 구현 아이디어는 없지만 구상을 적어보면:

  • unittest 테스트 러너를 상속 받은 별도의 테스트 러너를 만들고 이를 주 테스트 러너로 사용합니다. 이 테스트 러너는 어떤 테스트 케이스들이 실패하는지, 스킵해야하는지 목록을 함께 받습니다.
  • 테스트 목록은 <testcase>: FAILURE | SKIP 같은 꼴의 배열입니다. 값에 따라 스킵하거나, 실행하고 실패하도록 합니다. 각 운영체제 별로 관리합니다. (e.g., testhints/linux-arm64.json)

다만 데코레이터 및 주석을 다는 방식의 경우 TODO: RUSTPYHON 같은 꼴로 검색하여 작업 대상을 찾음과 동시에 테스트의 소스코드도 볼 수 있게 해주므로, 이를 본 아이디어처럼 변경하게 되면 이런 장점을 잃을 우려가 있습니다.
그리고 이 아이디어는 제가 실제로 구현을 시도해본적이 없어 다소 이상한 아이디어일 수 있습니다.

Footnotes

  1. https://github.com/RustPython/RustPython/blob/d7a885cea82b39be21b0f847e83cb78e117ff6ca/scripts/lib_updater.py 2

  2. https://github.com/RustPython/RustPython/blob/d7a885cea82b39be21b0f847e83cb78e117ff6ca/scripts/auto_mark_test.py 2

  3. https://github.com/RustPython/RustPython/pull/6796 2

  4. https://github.com/RustPython/RustPython/blob/82a8f67c71f99532ce4b4e9735149b536a30030e/.claude/commands/upgrade-pylib.md 2

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRequest for comments

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions