Typer Support¶
Experimental
patch_typer() does not work with typer>=0.26.0. See Typer 0.26+ Support below.
Experimental
This feature is still experimental. Please report any bugs or issues you run into!
You don't need to use rich-click directly to get access to a lot of rich-click's great features.
If you are a fan of Typer and you'd like to use rich-click's themes, you can patch Typer to use rich-click
and set the global config's THEME (and other config options) to whatever you want.
All you need to do is import patch_typer() from rich_click.patch.
This does not need to be done at the top of the file, in fact you can do it right before calling app.run() or typer.run(...):
# /// script
# dependencies = ["rich-click>=1.9", "typer>=0.15"]
# ///
from enum import Enum
import typer
from typing_extensions import Annotated
class LogLevel(str, Enum):
debug = "debug"
info = "info"
warn = "warn"
error = "error"
def main(
id: Annotated[int, typer.Argument(min=0, max=1000)],
age: Annotated[int, typer.Option(min=18)] = 20,
score: Annotated[float, typer.Option(max=100)] = 0,
log_level: Annotated[LogLevel, typer.Option(rich_help_panel="Logging")] = LogLevel.info,
color: Annotated[bool, typer.Option(rich_help_panel="Logging")] = True,
) -> None:
print(f"id={id} age={age} score={score} log_level={log_level} color={color}")
if __name__ == "__main__":
from rich_click.patch import patch_typer
import rich_click.rich_click as rc
patch_typer()
rc.THEME = "star-slim"
typer.run(main)
Output
Running the above Typer CLI works the same as you would otherwise expect, except now with a rich-click theme applied:
Patching Typer via the rich-click CLI¶
You can also patch Typer as an end-user of any Typer CLI via the rich-click CLI.
rich-click my-typer-cli --help
In addition to giving access to rich-click's themes, another benefit of this is being able to generate HTML and SVG help text easily. Although, do note that Typer and rich-click have some minor differences in how they render help text.
More information about usage of the rich-click CLI is in the rich-click CLI docs, or you can run rich-click --help to view the CLI.
Typer 0.26+ Support¶
Starting with typer==0.26.0, Typer vendors its own internal fork of Click (typer._click) instead of subclassing
the click package directly. typer.core.TyperCommand, TyperGroup, TyperOption, and TyperArgument no longer
derive from click.Command, click.Group, click.Option, or click.Argument. They derive from Typer's own
private copies of those classes, built with a different metaclass (abc.ABCMeta).
patch_typer() works by building new classes that inherit from both Typer's classes and rich-click's own
RichCommand / RichGroup / etc. (which are built on the real click classes). Since typer>=0.26, those two
class hierarchies are unrelated and their metaclasses conflict, so building the patched subclass raises:
TypeError: metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases
patch_typer() catches this. It builds all four patched classes up front, inside a single try/except TypeError
block (a metaclass conflict or an unresolvable MRO are the only ways this construction can fail), and only swaps
them into Typer's internals once every one of them succeeds. When one fails, which is currently always the case on
typer>=0.26, patch_typer() emits a short RuntimeWarning pointing back to this section, leaves Typer's own
classes untouched, and returns without raising. Your program keeps running exactly as it would if patch_typer()
had never been called.
This means:
- Your Typer CLI keeps working normally on
typer>=0.26. - You won't get rich-click's themes or panels from
patch_typer(). Typer falls back to rendering help with its own (also Rich-based) formatter. - You'll see a
RuntimeWarningat the point wherepatch_typer()is called, which you can silence with the standardwarningsfilters if it's expected in your environment.
If you need patch_typer() to actually apply rich-click's theming, pin typer<0.26 for now. Patching against
Typer's private, vendored fork of Click would mean re-implementing this logic against internals that could change
again with any future Typer release, so there is no fix planned at this time. Track
issue #330 for updates.