Skip to content

Conversation

oefe
Copy link
Contributor

@oefe oefe commented Jan 19, 2025

Lazy-load rich_utils (and rich.traceback) to reduce import time.

Fixes #744

There was some discussion in that issue about using lazyasd and if TYPE_CHECKING: in order to make the import known to type checkers and build tools that perform import analysis.

To keep it simple, I didn't go that route.
There are already a few lazy imports, so I think this doesn't change the big picture.
And lazyasd appears to be no longer maintained.

Let me know if you rather would use lazyasd and/or if TYPE_CHECKING:.

Benefit

This cuts down startup overhead by 45%. Below are some measurements (best of 5 runs).
Measured on a Mac mini (M4 pro).

Before

~/R/typer►time uv run docs_src/first_steps/tutorial001.py
Hello World

________________________________________________________
Executed in   94.19 millis    fish           external
   usr time   71.72 millis    0.15 millis   71.57 millis
   sys time   19.54 millis    1.76 millis   17.77 millis

before

After

~/R/typer►time uv run docs_src/first_steps/tutorial001.py
Hello World

________________________________________________________
Executed in   51.99 millis    fish           external
   usr time   37.36 millis    0.10 millis   37.26 millis
   sys time   12.77 millis    1.37 millis   11.39 millis

after

@svlandeg svlandeg added the feature New feature, enhancement or request label Jan 20, 2025
@bonastreyair
Copy link

I am also looking forward for this quick win, CLI should be faster than it is right now...

@svlandeg svlandeg self-assigned this Feb 17, 2025
@svlandeg svlandeg changed the title Lazy-load rich_utils to reduce startup time ⚡️ Lazy-load rich_utils to reduce startup time Feb 20, 2025
@N-Demir
Copy link

N-Demir commented Feb 25, 2025

+1

Copy link

@stinovlas stinovlas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me (I'm not a maintainer though).

I did follow the review process, downloaded and ran the code. Everything seems to be fine. This PR doesn't add any tests and I think that's OK, because the code is already tested and it's not changing behavior, it's just a performance optimization.

@oefe
Copy link
Contributor Author

oefe commented Mar 27, 2025

Originally, I thought the same. While the speedup is significant, it would be difficult to test reliably in a unit test, and I didn't want to introduce a flaky test.

But thinking about it again, I realize that it would be possible to assert that rich is not imported. This is deterministic and should be testable.

@oefe
Copy link
Contributor Author

oefe commented Mar 29, 2025

OK, added a test

Copy link
Member

@svlandeg svlandeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clear analysis and PR proposal @oefe!

I can definitely see the appeal of doing the lazy load to improve startup time when rich isn't used. It clutters the code a little bit, but the trade-off may be worth it.

One question I have is for when you do have Rich installed. Do the multiple import statements at different points in the code base result in an efficiency loss somehow? Because I can imagine that in the background, it is being checked what is already imported and what isn't. I'm not sure if that check leaves a performance trace or not.

@svlandeg svlandeg removed their assignment Apr 17, 2025
@oefe
Copy link
Contributor Author

oefe commented Apr 17, 2025

The performance impact is negligible. Essentially, it boils down to one dictionary lookup (to test whether the module is already in says.modules) and one dictionary insertion (add the module to the local namespace). These are both very quick.

Also, most of the affected lines are executed at most once per run.

Copy link
Member

@svlandeg svlandeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance impact is negligible.

Thanks for double checking!

This PR looks good to merge to me, I'll leave it to Tiangolo for a final review.

@onyx-and-iris
Copy link

onyx-and-iris commented Jun 8, 2025

I just tested these changes, it makes a noticeable difference for my CLI.

https://gist.github.com/onyx-and-iris/987ad066cb9baa03b58f5f446f7ea837

Thank you for investigating this issue.

onyx-and-iris added a commit to onyx-and-iris/obsws-cli that referenced this pull request Jun 19, 2025
Copy link

@basnijholt basnijholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Came here after I found out that Typer is the one package that is the slowest part of (my CLI).

@basnijholt
Copy link

@tiangolo, it seems like this PR is blocked by your review, would you mind giving it a few minutes of your time?

In my application agent-cli, startup time is extremely important and typer is responsible for 60% of all of it. This few line change will solve most of the problem!

image

@chrisgoddard
Copy link

+1 on this

@N-Demir
Copy link

N-Demir commented Jul 21, 2025

+1 !!!

Copy link
Member

@tiangolo tiangolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thank you @oefe 🙌

In particular, for the detailed description, benchmarks, and explaining the train of thought in all the points.

Thanks all for the help!

And thanks @svlandeg for the help maintaining as always. 😄


This will be available in Typer 0.17.0 in the next few hours. 🎉

@tiangolo tiangolo merged commit f398fb1 into fastapi:master Aug 30, 2025
24 checks passed
@dwreeves
Copy link

dwreeves commented Sep 3, 2025

@tiangolo There is still one additional performance improvement missing, not on CLI execution but for CLI help text rendering.

Specifically-- lazy-loading from rich.markdown import Markdown significantly improve performance of help text rendering for all users not using markdown because the downstream markdown_it import takes a ton of time. Most users don't set help text rendering to "markdown" mode so for most users this is a major performance improvement for --help.

There are many libraries in the wild which take a long time to load their CLIs (for reasons unrelated to Typer) and so every fraction of a second shaved off can make things feel snappier basically for free.

@svlandeg
Copy link
Member

svlandeg commented Sep 3, 2025

@dwreeves: thanks for the note. Please start a new discussion for this, as this PR has been merged and closed. When opening the discussion, it'd be great if you can quantify the improvement you get from lazy loading rich.markdown.

g-arjones added a commit to g-arjones/typer that referenced this pull request Sep 4, 2025
rich.markup is used to escape strings when rendering help texts.

This fixes a regression introduced by fastapi#1128
JulioLoayzaM added a commit to quarkslab/crypto-condor that referenced this pull request Sep 8, 2025
Typer 0.9.4 is not compatible with click 8.2.1 due to a missing
positional argument in rich_utils.py:370, so update Typer to the latest
version.

This introduced a breaking change, as the rich_utils module imported to
override the dim text in help messages is now lazy-loaded [1]. The
solution is to directly import rich_utils from typer.

Typer was not updated due to using the caret requirement [2]. The
constraint stays for now, considering potential breaking changes
introduced in new versions. However, the constraint on lief changes
~=0.16, a compatible release [3] allowing >=0.16.0 and <1.0.0.

[1]: fastapi/typer#1128
[2]: https://python-poetry.org/docs/dependency-specification/#caret-requirements
JulioLoayzaM added a commit to quarkslab/crypto-condor that referenced this pull request Sep 8, 2025
Typer 0.9.4 is not compatible with click 8.2.1 due to a missing
positional argument in rich_utils.py:370, so update Typer to the latest
version.

This introduced a breaking change, as the rich_utils module imported to
override the dim text in help messages is now lazy-loaded [1]. The
solution is to directly import rich_utils from typer.

Typer was not updated due to using the caret requirement [2]. The
constraint stays for now, considering potential breaking changes
introduced in new versions. However, the constraint on lief changes
~=0.16, a compatible release [3] allowing >=0.16.0 and <1.0.0.

[1]: fastapi/typer#1128
[2]: https://python-poetry.org/docs/dependency-specification/#caret-requirements
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature, enhancement or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants