fix: faster CLI on Python 3.15#2797
Conversation
5c5d1e9 to
7893835
Compare
|
henryiii#17 defers more imports to be 1.4x faster than this PR and 4x faster than upstream |
|
@henryiii should this land in rc2? |
|
This can land soon, but no need to be in 4.0 exactly. It's not a breaking change or anything. |
564a09c to
57b1173
Compare
There was a problem hiding this comment.
Pull request overview
This PR significantly speeds up the cibuildwheel CLI startup time (3-4x faster on Python 3.15a7) by leveraging Python 3.15's lazy imports feature (PEP 810). It does this by:
- Adding
__lazy_modules__lists to most modules (generated by the author'sflake8-lazytool) to mark which imports should be deferred until first use. - Replacing
from typing import TYPE_CHECKINGwith a module-levelTYPE_CHECKING = Falseconstant, so that thetypingmodule itself can be lazily imported. - Moving type-only imports (e.g.
Self,Literal,Sequence,Mapping,Any, etc.) intoif TYPE_CHECKING:blocks, and addingfrom __future__ import annotationsto files that need it so runtime annotation evaluation isn't needed. As a side effect,Selfreferences inArchitectureand a few other classes are replaced with the concrete class name (and some@classmethods become@staticmethods). - Adding a ruff rule to ban
typing.TYPE_CHECKINGimports in favor of the newTYPE_CHECKING = Falsepattern.
Changes:
- Add
__lazy_modules__lists and reorganize imports throughout the package for PEP 810 lazy loading. - Replace
from typing import TYPE_CHECKINGwithTYPE_CHECKING = False, and ban the old form via ruff. - Rework
Architecture/EnableGroup/OCIPlatformto dropSelftyping in favor of concrete class names (converting someclassmethods tostaticmethods inArchitecture).
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| pyproject.toml | Ban typing.TYPE_CHECKING imports via ruff banned-api rule. |
| cibuildwheel/main.py | Add lazy modules list; move type-only imports into TYPE_CHECKING. |
| cibuildwheel/architecture.py | Lazy imports; replace Self-typed classmethods with staticmethods referencing Architecture directly. |
| cibuildwheel/audit.py | Add __lazy_modules__ list. |
| cibuildwheel/bashlex_eval.py | Add __lazy_modules__ list. |
| cibuildwheel/ci.py | Add __lazy_modules__ list. |
| cibuildwheel/environment.py | Lazy imports; defer typing/collections.abc imports. |
| cibuildwheel/errors.py | Add __lazy_modules__ list. |
| cibuildwheel/extra.py | Lazy imports; defer typing imports. |
| cibuildwheel/frontend.py | Lazy imports; defer typing/Sequence imports. |
| cibuildwheel/logger.py | Lazy imports; switch to TYPE_CHECKING = False. |
| cibuildwheel/oci_container.py | Lazy imports; drop Self return type on OCIPlatform.native. |
| cibuildwheel/options.py | Lazy imports; defer typing imports. |
| cibuildwheel/platforms/*.py | Lazy imports across all platform modules. |
| cibuildwheel/projectfiles.py | Lazy imports; defer pathlib/typing. |
| cibuildwheel/resources/* | Add __lazy_modules__ lists in resource scripts. |
| cibuildwheel/schema.py | Lazy imports. |
| cibuildwheel/selector.py | Lazy imports; replace Self with EnableGroup. |
| cibuildwheel/util/*.py | Lazy imports across util modules. |
| cibuildwheel/venv.py | Lazy imports; defer Sequence import. |
| unit_test/* | Use TYPE_CHECKING = False pattern in tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Lazy: Before this PR (still on 3.15): |
|
🏎️🏎️🏎️ |
|
Hmm. I'm not fully convinced on this... cibuildwheel isn't really a tool where the speed of startup is a big factor. If this was a low-cost change, maybe it would be worth it, but, Github says this PR is On top of that, how do we ensure the values of I guess I feel that it's quite a lot of extra lines for fairly negligible user benefit, and more maintainer work to maintain and review these extra annotations. |
|
This feels much snappier on the command line when asking for help. And it also means that anything we don't use isn't imported. The TYPE_CHECKING is guaranteed by the formatter. The Another option is: class AllLazy:
@staticmethod
def __contains__(_: str) -> bool:
return True
__lazy_modules__ = AllLazy()This makes everything lazy, so if we needed non-lazy imports, that would be a problem, but I don't think we do. A lot of this extra text will go away once we support 3.15+ only, as then it becomes a |
|
I think we can get away without many of the typing changes. Only TYPE_CHECKING is triggering the import, on 3.14+ annotations are lazy so don't trigger lazy imports in 3.15. |
|
With only #2864 and |
0699040 to
bc01bb2
Compare
|
If we move to enforcing ruff @henryiii, are you planning to add some configuration to flake8-lazy ? I think from the readme the answer is no but "like a list of modules that can't be lazy imported" would be nice to reduce the patch here. It seems python always imports some modules so maybe those could be excluded by default (likely subject to change so maybe not a good idea). module listRunning module listraw: curated: |
No, the focus is making it work well out of the box, but not against configuration forever. I'd be a lot happier with it if flake8 didn't require a new, specific-to-flake8-only file... I think ignoring things that get imported anyway is likely fine for a default. |
|
If we can make this tool-assisted, so I can effectively ignore the I still feel that the I'm better inclined to the |
5af2aa4 to
8dbaa59
Compare
c3b650c to
c715167
Compare
|
Switched to using auto fixer mode rather than the flake8 runner (0.8 now respects black/ruff style formatting). With henryiii/flake8-lazy#73 we could have config, too. |
899fb7a to
8ff54de
Compare
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Assisted-by: ClaudeCode:claude-sonnet-4.6 Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Assisted-by: ClaudeCode:claude-sonnet-4-6
8ff54de to
cd79b86
Compare
mayeut
left a comment
There was a problem hiding this comment.
I'm ok with the current state of the PR.
One minor comment:
- we could leverage flake8-lazy configuration to reduce the " increase in ugliness"
I started to work on something that addresses the comment if there's interest. It comes at the expense of a new nox session to rebuild the config and one ugly long line in the config.
This makes running
cibuildwheel --helporcibuildwheel --print-build-indentifiersaround 3-4x faster on Python 3.15a7 by making imports lazy. If usinguvon a first run (like withuvx), this likely is even more, since it doesn't pre-compile.pycfiles by default.I used my
flake8-lazypackage (post here) to come up with the__lazy_modules__lists, and GPT 5 (due to the 0x token usage) in VSCode's copilot to apply the lists to the files.I restoredI think it's fine, but #2799 and #2798 were exposed while trying this. Will minimize the diff after those.from __future__ import annotations, but maybe I didn't need to, since they are not resolved on 3.14+. I might try without too later.Edit: used the new
--applyfeature of flake8-lazy to create this instead.Here's the version with @hugovk's CPython fix (thanks to @hugovk!):