Skip to content

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

Open

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.py1와 scripts/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>.py 랑 delta 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

Activity

  1. youknowone commented on Jul 15, 2025

    @youknowone
    Member

    Recently @arihant2math wrote a helpful script
    Could you try this script? https://github.com/RustPython/RustPython/blob/main/scripts/fix_test.py

  2. moreal commented on Jul 15, 2025

    @moreal
    ContributorAuthor

    Yeah, I saw that. While it's not good to have work stalled by pursuing perfectionism, this issue shows traces of that exact concern. Initially, I thought simply marking the failed tests locally would suffice, but then considered that tests might fail on other operating systems too, and felt we'd need to cover those cases for true automated operation. Of course, even then it wouldn't cover all scenarios, so I might be getting unnecessarily bogged down in minor details... 😢

  3. youknowone commented on Jul 15, 2025

    @youknowone
    Member

    To be honest, I haven’t thought very deeply about this issue, so I believe others who have considered it more seriously are better positioned to discuss it.

    First, I haven't given much thought to the problem of tracking the base version of each file, because in most cases, git log Lib/.py gives a reasonably accurate answer. Of course, this approach has its downsides. it doesn't work well when dealing with multiple files, and it's difficult to visualize. However, in practice, it hasn’t significantly interfered with the workflow of updating necessary libraries.

    The major downside is that the update process still requires a substantial amount of manual effort. Ideally, we could specify a target Python version and run a script to update all files accordingly. But in many cases, replacing a Python file is not isolated. it’s tied to other Python files or even to updates in Rust code. Even if we assume that it’s possible to switch the target Python version with a single large patch, given RustPython’s current level of compatibility, we can’t know when such a patch would be mergeable. For example, while most of the codebase was compatible with Python 3.9–3.11, features related to async remained stuck at around the 3.6 level for a long time.

    Realistically, to carry out automation at a mergeable scale, we need automation that works per module and includes dependency handling. A reasonable level of automation for now might involve combining fix_test.py with GitHub Actions to enable automated processing across all platforms.

    As for avoiding having to manually mark each function with @Skip or @expectedfailure, I’m not exactly sure what the best solution would be. But based on my experience maintaining the project, I believe two conditions must be met.

    First, we must avoid patching Lib modules like unittest directly, because we update these libraries about once a year, and custom patches make that harder.
    Second, we need to be able to detect regressions immediately. We're not running tests just for statistical analysis. we're running them to know when actual regressions happen.

    So, having a centralized list somewhere that controls test expectations and editing that list to change unittest results would be a possible approach. It may be slightly inconvenient when writing patches, but I think it's a reasonable trade-off between ease of library updates and the cost of writing proper bugfix patches. If the upgrade script can add new expectedFailure functions to the existing list but cannot remove them, then it should be acceptable to review regressions manually.

  4. reactive-firewall commented on Jul 17, 2025

    @reactive-firewall

    Here’s a comprehensive summary of the work on #5974 and the related discussion and prototype, tailored for other developers interested in the project:


    Background & Motivation

    RustPython aims to provide a Python 3 interpreter written in Rust, maintaining a clean codebase and closely tracking Python 3.x compatibility. Updating the Python standard library (Lib/) from CPython in RustPython has been labor-intensive and error-prone, especially for test files. Key challenges include:

    • Manual copying and patching of files from CPython.
    • Difficulty tracking which CPython version each file is based on.
    • Labor-intensive manual addition of markers like @unittest.expectedFailure and # TODO: RUSTPYTHON.
    • Complicated merging, especially when CPython upstream changes.

    RFC & Discussion Highlights (Issue #5974)

    @moreal proposed a more automated, robust, and reproducible workflow for updating CPython standard library files in RustPython, @youknowone then proposed narrowing the scope to be particularly focused on the test suite. Major discussion points:

    Their Proposal

    • Automated Scripted Updates: Use GitHub Actions to automate copying, patching, and testing CPython files (with OS matrix testing).
    • Version Tracking: Improve traceability of which CPython version each file is based on.
    • Automated Test Outcome Reporting: Collect test results (success/failure/panic) in JSON across OSes, enabling better planning for what needs patching.
    • Automated Decorator Management: Use tools like LibCST for programmatic insertion/removal of decorators and comments, reducing manual labor.
    • Separation of Reference and Local Changes: Propose keeping original CPython files unchanged where possible, tracking RustPython-specific modifications separately.

    Community Feedback & My Comments


    Experimental Implementation (Prototype)

    I've designed and prototyped a set of GitHub Actions and supporting workflow files that implement much of your proposal: Demo Results

    Key Components

    • Custom Actions

      • .github/actions/CI-5974-Fetch-CPython/action.yaml: Checks out a specified CPython version, pulls the desired files (using glob patterns), and prepares them for integration.
      • .github/actions/CI-5974-Fetch-RustPython/action.yaml: Checks out and builds RustPython at the requested commit/ref.
      • .github/actions/CI-5974-Integrate-CPython/action.yaml: Integrates selected CPython files into the RustPython tree, handling errors and warnings gracefully.
    • Workflow

      • .github/workflows/Check_Tests.yml: Orchestrates my experiment of full integration and testing cycle across OSes and Python versions. It:
        • Fetches RustPython and CPython source files via custom actions.
        • Copies reference files via custom actions, and attempts to run them using RustPython.
        • Tries auto-fixes for failing tests, reverts broken integrations, and tracks outcomes.
        • Runs tests both directly (if unittest.main() present) and via the modular test runner for more complex cases.
        • All testing is done in sub-processes and hung/deadlocked tests are timed-out.

    Technical Highlights

    • Matrix Testing: Runs across multiple OSes and Python versions, including experimental future releases.
    • Timeout Handling: Detects and mitigates hanging tests, which is crucial for CI robustness.
    • Outcome Recording: Tracks whether direct copies or auto-fixes succeed, fail, or are reverted, and provides clear CI output.
    • Non-destructive Integration: Attempts to integrate new reference files and falls back gracefully if integration fails.
    • Extensible Design: Modular actions and workflow steps allow future enhancements (e.g., more granular test selection, better version tracking).

    Lessons Learned & Next Steps

    • Automation Reduces Manual Labor: Most integration work can now be scriptable, dramatically improving efficiency and reliability.
    • Edge Cases Remain: Not all files are directly runnable, and some require special handling. The workflow should log and skip these gracefully.
    • Collaboration Needed: Further refinement can benefit from input from maintainers and contributors (especially around handling edge cases and integrating with existing workflows).

    Summary for Developers

    If you’re interested in improving how RustPython keeps its standard library and tests up-to-date with CPython:

    • Review the RFC and discussion in Issue #5974.
    • Examine the prototype diff for a working example of automated integration and testing.
    • Try the workflow locally or in CI—it’s designed to be modular and extensible.
    • Contribute ideas for handling edge cases, improving version tracking, or making the workflow more maintainable.

    This experiment demonstrates a concrete way forward for sustainable, automated updates of RustPython’s CPython-derived files, with clear opportunities for collaboration and improvement.


    TL;DR My Outdated and Historical Comments and Edits

    Regarding @youknowone's suggestion

    A reasonable level of automation for now might involve combining fix_test.py with GitHub Actions to enable automated processing across all platforms.

    any objection to me taking on this work to get my feet wet in the project. I'm very familiar with GHA and python's unittest module and actually discoverd rustpython when looking to expand my cross-python tests to other python implementations.

    Plus, I'm wanting more test-run diversity information to go off before responding to the rest of the topic as a whole. I'm curious if we could tree-build with the AST module for traversing the python and then somehow modify the resulting tree when adding the rust specific stuff.

    EDIT: TL;DR - My idea is more an experiment to blind test the testing workflow
    regarding what I was thinking here (for clarity):

    • Work on a fork (to keep prototyping CI usage down) but still tag this GHI (for tracking)

    • a bare-basic GHA workflow (triggered by any push to "*.py" files to start / manual trigger for testing)

    • implement a separate reusable action (concluding only with pass/canceled/skipped/neutral results at first) to run per input tuple of (runner OS image (so we can handle diff arch/versions too), implementation (e.g., cpython ref versus rustpython), git-ref/version, env_overrides, and other optional variables with defaults as they make sense to the implementation)

      • this will be called as a reusable @uses: rust-python-ci-min-tests (name is WIP) etc.
      • will allow common input/output usage later on
      • will handle fetching/bootstrapping the given source implementation
      • performing the actual test run
      • evaluating the result as pass/neutral (e.g. non-blocking result, but with test metrics for us humans)
      • handling overhead of multi-platform quirks of (windows, macOS and Ubuntu to start)
    • these can then be used to gain more insight for this discussion


    EDIT (Continued):

    [!IMPORTANT]
    Disclaimer: I am just a fellow user of rustpython and have no official association with the project or its maintainers. Any feedback or comments I provide are based on my personal experience and are offered AS-IS. I cannot guarantee their accuracy or effectiveness, and they should not be considered official support or advice. Please reach out to the maintainers for authoritative guidance.

    This may be know to some already, however, I'm including it here to start some kind of documentation on it for this broader topic.

    TDIL

    So first thing I discovered, is that pulling the rustpython repo from it's tip (e.g. main) will be problomatic, it is quite possible to just fail to compile before it can run a single cpython test. I'm not sure on the "best" way to solve this, but one way is to use a staging branch (e.g., when the normal CI/CD is able to build or some low-bar definition of 'semi-stable' automatically tag or merge to some staging branch, for these kinds of integration updates from cpython)

    Second, we'll want test file (e.g., per Lib/test/*.py file) level control over integrating from Cpython, which is totally do-able. Beyond that looking at the individual tests will be out of my experiment's scope for now.

    Third, this experiment will take me a long time (especially because I'm only working on it in my spare time) so the curious may take a look at the experiment over there

    Fourth, the experiment's first milestone will be to have an automation with a single successful test run of the fix_test.py script in CI (not happening this week)

    Thanks for reading this far 🙇 I hope this comment helps in some small way 🙊


    In short

    What if we store the built binary from main in cache, and pull it via the dedicated workflow to run the workflow that you're working on? That way your workflow will stay in scope of what matters, as it's redundant to see if the program compiles, we already have a process for that

    I totally agree that "it's redundant to see if the program compiles." Currently, I'm only doing the redundant rebuilds on my development branch while prototyping, as I've disabled the normal build workflows during my experiments. This is one of the implementation details I plan to explore further.

    Initial thoughts in more detail

    TL;DR: Long-term, using cached builds is ideal, but during development, it has limitations. There’s a case to be made for building and caching each commit where core Rust files change, and once for each target (like OS, Rust compiler version, and architecture). Each target version of CPython could then use the same cached RustPython for testing. For now, I’m simplifying this by targeting a single Rust compiler version, but once we have a stable release for RustPython, it will be important to track regressions. This is crucial for identifying issues when a new Rust compiler version causes a regression. Regression testing like this limits our use of cached builds since we would already know the cached build works.

    We still need to rebuild sometimes to test our changes, which could lead to unexpected regressions. These are the cases I was referring to when I mentioned it could be "problematic." I meant to highlight the dependency of CPython’s tests on stable build branches. Here’s a simplified code snippet to illustrate:

      rust-python-ci-min-tests:  # (name is WIP)
        needs: [rust-python-build-job]
        if: ${{ needs.rust-python-build-job.outcome == success() }} # syntax simplified for clarity
        # rest of job

    My suggested solution:

    I believe it would be effective to implement logic that inputs the build outcomes from existing jobs (as @ShaharNaveh correctly suggested) and dynamically rebuilds only when necessary. This means if a development branch (e.g., not main) fails to build, we could still rebuild from main (a known working branch) and cache the binary. We could then integrate the 'pure' Python source and CPython's test source for comprehensive regression testing.

    I plan to handle this by adding optional inputs to the reusable workflow I'm developing. These inputs would allow us to bypass caching (which I intend to have as the default) and force rebuilds when needed. This approach aims to future-proof the workflow before integrating it with the core project, following the open-closed design principle.

    I’m still figuring out how to differentiate the cache hash and whether to use the existing actions/cache (which is more maintainable but less flexible) or to pass builds via artifacts (actions/upload-artifact and actions/download-artifact) (which is more flexible but slightly more complex). I’m eager to learn more about the RustPython project and its requirements to make the best choice here.

    Hopefully this helps! 🙇 Thanks for reading this far.


    any objection to me taking on this work

    @reactive-firewall you can comment the word take and this issue will be assigned to you

    @ShaharNaveh, Thanks, that is good to know! However, I don't want to over-commit on this, I only intend on looking into the mentioned automation sub-task, leaving the rest alone:

    Namely I'm focused on:

    A reasonable level of automation for now might involve combining fix_test.py with GitHub Actions to enable automated processing across all platforms.

    Namely Ignoring:

    ... and the other is that it's difficult to easily determine which Python version each file is based on.
    ... 하나는 각각의 파일들이 현재 Python 몇 버전 기준 파일인지 쉽게 알기 어렵다는 점입니다.


    I initially tried to create a new GH issue with this ( #5974 ) as the parent issue but lack the necessary permissions; If this is somehow an issue for project tracking, I apologise for that, and someone with the necessary permissions may create the sub-issue and assign that sub-issue to me for tracking as needed.


    You could as a first step have them in a "do not touch" list, so you won't update them, whether it's the entire directory or specific file inside those problematic directories.

    Instead of doing everything in one go, treat it as a migration process where files who are not in the "do not touch" list will forever(?) be updated, it will require a lot of tinkering but imo that's the way to go, would love to hear what other people think

    Thank you for your suggestion regarding the "do not touch" list. I appreciate your insights, and I am currently experimenting with a similar approach, starting with an empty list. I found some heuristics from the CPython dev-docs that may be useful as well.

    @ShaharNaveh, it seems we share a similar mindset on the need for granular control over the tooling. I am focused on the research phase of my experiment (e.g. all that "tinkering" you mentioned). I'd also wish to hear what other people think. To update the discussion on my existing design, here’s a summary of my experimental code:

    AI analysis so-far

    GitHub Actions Workflow Analysis: CI-5974

    Overview

    • Name and Description: The workflow is named CI-5974 and is described as a continuous integration workflow for GHI 5974.
    • Trigger Events: The workflow is triggered on:
      • Push events to any branch (branches: ["**"]).
      • Push events for tags that start with "v" (tags: ["v*"]).

    Environment Variables

    The workflow defines several environment variables related to Python versions:

    • PYTHON_DEFAULT: The default Python version.
    • PYTHON_OLD_MIN: The oldest supported Python version.
    • PYTHON_OLD_EXTRA: Additional older Python versions for extra coverage.
    • PYTHON_EXPERIMENTAL: Future Python versions for testing.
    • TEST_MATCH_PATTERN: A pattern to match test files in the Lib/test directory.

    Jobs

    Job: TEST-5974

    • Permissions: The job has specific permissions set for actions, contents, statuses, and pull requests, while denying access to packages and security events.
    • Conditions: The job will not run if it is cancelled.
    • Execution Environment: It runs on a matrix of operating systems (ubuntu-latest, macos-14, macos-15, windows-latest) and Python versions defined in the environment variables.
    • Timeout: The job has a timeout of 30 minutes.
    • Continue on Error: It can continue on error if the matrix variable experimental is set to true.

    Strategy

    • Matrix Strategy: The job uses a matrix strategy to run tests across different OS and Python versions, including experimental configurations.
    • Fail-Fast: The fail-fast option is set to false, meaning that all jobs will run even if one fails.

    Steps

    1. Checkout RustPython Repository: Uses the actions/checkout action to check out the RustPython repository.
    2. Pre-Test Build Check: Runs a build check to ensure the RustPython can be built successfully.
    3. Fetch CPython: Checks out the CPython repository to fetch the reference implementation.
    4. Enumerate Reference Lib Files: Lists the test files in the CPython repository and outputs them for further processing.
    5. Show CPython License: Displays the license of the CPython repository.
    6. Integrate CPython Test Files: Copies the reference implementation tests from CPython to RustPython.
    7. Bootstrap Python: Sets up the Python environment for testing.
    8. Try Smoke Testing: Runs smoke tests on the integrated test files, handling potential failures and attempting to auto-fix issues.
    9. Post-Clean: A cleanup step that always runs, ensuring that the workflow does not break on regression.

    Key Features

    • Error Handling: The workflow includes various error handling mechanisms, such as warnings for missing files and conditions to skip tests that cannot be run directly.
    • Experimental Features: The use of experimental Python versions indicates that the workflow is designed to test new features or changes in Python.
    • Modular Steps: Each step is modular, allowing for easier debugging and maintenance. The use of shell scripts within steps provides flexibility in executing commands.

    TODOs and Future Work

    The workflow contains several TODO comments indicating areas for improvement or features that are still in progress, such as:

    • Moving certain steps to separate actions for better organization.
    • Implementing more robust filtering for reference library usage.
    • Addressing specific issues with Windows timeouts during testing.

    Conclusion

    The CI-5974 workflow is a comprehensive CI setup for testing a project that integrates RustPython with CPython. It is well-structured, utilizing matrix builds to cover multiple environments and Python versions, while also incorporating error handling and modular steps for clarity and maintainability. The presence of TODOs suggests ongoing development and refinement of the workflow.


    [!NOTE]
    As my code is merely experimental, I recognize that parts may need refactored to be of any use to RustPython, especially the duplication of what is already in other CI workflows. I appreciate any early feedback! 🙇

    Regarding known files and directories that currently break the test suite if updated:

    • Lib/test/libregtest
    • Lib/unittest/
    • Lib/test/support
    • Lib/importlib
    • probably more 😁

    This insight is valuable! Additionally, could anyone clarify why Lib/test/libregtest and Lib/test/support cause issues? A reference to a relevant GHI would be helpful.

    There are indeed more 🙊 e.g., test_builtins.py will hang


    Feel free to try and open a GH issue about any errors that you encounter

    Understood, I'm only dealing with {RustPython,cpython}/Lib/test/*.py at the moment, as the RustPython docs mentioned already porting unittest over, and importlib makes sense with the C-lang and Rust backends potential for providing different binary interfaces.

    Second, we need to be able to detect regressions immediately. We're not running tests just for statistical analysis. we're running them to know when actual regressions happen.

    I'll continue with the blackbox testing approach for my experiment regarding {RustPython,cpython}/Lib/test/{support,libregtest} and open/update relevent issues if they actually error, inline with the rationale previously mentioned. This will allow me to build the comprehensive list empirically as well.

    EDIT: I've hidden my previous comments to help manage the long-length of the discussion.

  5. ShaharNaveh commented on Jul 18, 2025

    @ShaharNaveh
    Contributor

    any objection to me taking on this work

    @reactive-firewall you can comment the word take and this issue will be assigned to you

  6. ShaharNaveh commented on Jul 18, 2025

    @ShaharNaveh
    Contributor

    So first thing I discovered, is that pulling the rustpython repo from it's tip (e.g. main) will be problomatic, it is quite possible to just fail to compile before it can run a single cpython test.

    What if we store the built binary from main in cache, and pull it via the dedicated workflow to run the workflow that you're working on? That way your workflow will stay in scope of what matters, as it's redundant to see if the program compiles, we already have a process for that

  7. reactive-firewall commented on Jul 21, 2025

    @reactive-firewall
  8. reactive-firewall commented on Jul 21, 2025

    @reactive-firewall
  9. ShaharNaveh commented on Jul 21, 2025

    @ShaharNaveh
    Contributor

    I plan to handle this by adding optional inputs to the reusable workflow I'm developing. These inputs would allow us to bypass caching (which I intend to have as the default) and force rebuilds when needed. This approach aims to future-proof the workflow before integrating it with the core project, following the open-closed design principle.

    Sounds good imo.

    I’m still figuring out how to differentiate the cache hash and whether to use the existing actions/cache (which is more maintainable but less flexible) or to pass builds via artifacts (actions/upload-artifact and actions/download-artifact) (which is more flexible but slightly more complex). I’m eager to learn more about the RustPython project and its requirements to make the best choice here.

    The general rule of thumb that I'm following is:

    • artifacts: share assets between jobs in the same workflow.
    • cache: share assets between different workflows & between workflow runs.

    For now if it simply things, assume that for the majority of files, if updated will not break RustPython. there are some known files & directories that are known to currently break the test suite if updated such as:

    • Lib/test/libregtest
    • Lib/unittest/
    • Lib/test/support
    • Lib/importlib
    • probably more 😁

    You could as a first step have them in a "do not touch" list, so you won't update them, whether it's the entire directory or specific file inside those problematic directories.

    Instead of doing everything in one go, treat it as a migration process where files who are not in the "do not touch" list will forever(?) be updated, it will require a lot of tinkering but imo that's the way to go, would love to hear what other people think

  10. 29 remaining items

  11. youknowone commented on Jan 18, 2026

    @youknowone
    Member

    @moreal we now have scripts/lib_updater.py and scripts/auto_mark_test.py for each file. This is not full-automation but helpful for each file. Could the issue to be updated to current status? We now need to regard higher level automation.

  12. moreal commented on Jan 18, 2026

    @moreal
    ContributorAuthor

    @moreal we now have scripts/lib_updater.py and scripts/auto_mark_test.py for each file. This is not full-automation but helpful for each file. Could the issue to be updated to current status? We now need to regard higher level automation.

    Okay, I'll update until tomorrow and let you know by mention. 🙆🏻

  13. moreal commented on Jan 20, 2026

    @moreal
    ContributorAuthor

    Okay, I'll update until tomorrow and let you know by mention. 🙆🏻

    @youknowone I'm sorry to be late. I've finished updating it now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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