Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add typer.set_rich_*() to allow Rich formatting to be disabled #647

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c39d2ef
Add named parameter 'rich_enable' to class Typer
JacobKochems Aug 2, 2023
6a4ba06
Refactor Typer parameter to a global one
JacobKochems Aug 2, 2023
67fad4b
Add a test case for `typer.enable_rich(<bool>)`
JacobKochems Aug 2, 2023
69a86d4
Fix wrong test name
JacobKochems Aug 3, 2023
98ea1c3
Make assertion more robust
JacobKochems Aug 3, 2023
b0abe95
Add a separate switch for Rich traceback
JacobKochems Aug 3, 2023
6f28107
Merge pull request #1 from JacobKochems/feature/enable-rich
JacobKochems Aug 3, 2023
f265dc2
Remove obsolete function
JacobKochems Aug 3, 2023
e29d295
Merge pull request #2 from JacobKochems/feature/enable-rich
JacobKochems Aug 3, 2023
87a9ce7
Update docs to reflect latest Rich output changes
JacobKochems Aug 3, 2023
08bc2f1
Merge pull request #3 from JacobKochems/feature/enable-rich
JacobKochems Aug 3, 2023
f42778f
Rename symbols to be more specific
Sep 19, 2023
6656674
Alias function with its namespace of origin
Sep 19, 2023
6093088
Auto format after rename/refactor
Sep 19, 2023
596c5f0
Rename symbols for better semantic precision
Sep 19, 2023
42cf404
Update docs to new nomenclature used in PR #647
Sep 19, 2023
7b19357
Feature/enable rich (#4)
JacobKochems Sep 19, 2023
6ff12c8
Rename tests to new nomenclature of PR #647
Sep 19, 2023
2ade3dc
Rename tests to new nomenclature of PR #647
Sep 19, 2023
76b0742
Update docs to reflect that functions are setters
JacobKochems Sep 20, 2023
6a7735e
Merge branch 'tiangolo:master' into feature/enable-rich
JacobKochems Nov 16, 2023
c408a39
Merge branch 'tiangolo:master' into feature/enable-rich
JacobKochems Dec 13, 2023
fc18ef2
Merge branch 'tiangolo:master' into feature/enable-rich
JacobKochems Feb 29, 2024
65ddbf0
Merge branch 'tiangolo:master' into feature/enable-rich
JacobKochems Mar 25, 2024
3f9bc07
Merge branch 'master' into feature/enable-rich
May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/tutorial/printing.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ In general, **Typer** tends to be the entry point to your program, taking the fi

The best results for your command line application would be achieved combining both **Typer** and **Rich**.

### If you don't want to use Rich formated output

In case you'd rather like to use the simple text ouput, but need **Rich** to be installed in your environment for any other reason, you can disable the use of **Rich** by **Typer**.
There are two functions in the **Typer** module to control the output format globally, affecting all `typer.Typer()` instances:
JacobKochems marked this conversation as resolved.
Show resolved Hide resolved

1. `typer.enable_rich_help`
2. `typer.enable_rich_traceback`

The first one controls all help and **Click's** exception messages. The second one controls the format of the stack trace.

## "Standard Output" and "Standard Error"

The way printing works underneath is that the **operating system** (Linux, Windows, macOS) treats what we print as if our CLI program was **writing text** to a "**virtual file**" called "**standard output**".
Expand Down
14 changes: 14 additions & 0 deletions tests/assets/enable_rich.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import typer

typer.enable_rich_help(False)

app = typer.Typer()


@app.command()
def main(arg: str): # pragma: no cover
pass


if __name__ == "__main__":
app()
14 changes: 14 additions & 0 deletions tests/assets/enable_rich_traceback_false.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import typer

typer.enable_rich_traceback(False)

app = typer.Typer()


@app.command()
def raise_():
raise ValueError # raise some error to test traceback output


if __name__ == "__main__":
app()
27 changes: 27 additions & 0 deletions tests/test_enable_rich.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import subprocess
import sys
from pathlib import Path


def test_enable_rich_help_is_false():
file_path = Path(__file__).parent / "assets/enable_rich.py"
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", str(file_path), "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
# assert simple help text
assert "─" not in result.stdout


def test_enable_rich_traceback_is_false():
file_path = Path(__file__).parent / "assets/enable_rich_traceback_false.py"
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", str(file_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
# assert simple help text
assert "─" not in result.stderr
2 changes: 2 additions & 0 deletions typer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

from . import colors as colors
from .main import Typer as Typer
from .main import enable_rich_help as enable_rich_help
from .main import enable_rich_traceback as enable_rich_traceback
from .main import run as run
from .models import CallbackParam as CallbackParam
from .models import Context as Context
Expand Down
15 changes: 11 additions & 4 deletions typer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,20 @@
except ImportError: # pragma: nocover
rich = None # type: ignore

_is_rich_enabled = True

if TYPE_CHECKING: # pragma: no cover
if _get_click_major() == 7:
import click.shell_completion

MarkupMode = Literal["markdown", "rich", None]


def set_rich_output(rich_enable: bool) -> None:
global _is_rich_enabled
_is_rich_enabled = rich_enable


# TODO: when deprecating Click 7, remove this
def _typer_param_shell_complete(
self: click.core.Parameter, ctx: click.Context, incomplete: str
Expand Down Expand Up @@ -232,7 +239,7 @@ def _main(
if not standalone_mode:
raise
# Typer override
if rich:
if rich and _is_rich_enabled:
rich_utils.rich_format_error(e)
else:
e.show()
Expand Down Expand Up @@ -262,7 +269,7 @@ def _main(
if not standalone_mode:
raise
# Typer override
if rich:
if rich and _is_rich_enabled:
rich_utils.rich_abort_error()
else:
click.echo(_("Aborted!"), file=sys.stderr)
Expand Down Expand Up @@ -725,7 +732,7 @@ def main(
)

def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
if not rich:
if not (rich and _is_rich_enabled):
return super().format_help(ctx, formatter)
return rich_utils.rich_format_help(
obj=self,
Expand Down Expand Up @@ -787,7 +794,7 @@ def main(
)

def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
if not rich:
if not (rich and _is_rich_enabled):
return super().format_help(ctx, formatter)
return rich_utils.rich_format_help(
obj=self,
Expand Down
22 changes: 20 additions & 2 deletions typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
import click

from .completion import get_completion_inspect_parameters
from .core import MarkupMode, TyperArgument, TyperCommand, TyperGroup, TyperOption
from .core import (
MarkupMode,
TyperArgument,
TyperCommand,
TyperGroup,
TyperOption,
set_rich_output,
)
from .models import (
AnyType,
ArgumentInfo,
Expand Down Expand Up @@ -49,6 +56,17 @@
_original_except_hook = sys.excepthook
_typer_developer_exception_attr_name = "__typer_developer_exception__"

_is_rich_traceback_enabled = True


def enable_rich_help(enable: bool) -> None:
set_rich_output(enable)


def enable_rich_traceback(enable: bool) -> None:
global _is_rich_traceback_enabled
_is_rich_traceback_enabled = enable


def except_hook(
exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType]
Expand All @@ -68,7 +86,7 @@ def except_hook(
click_path = os.path.dirname(click.__file__)
supress_internal_dir_names = [typer_path, click_path]
exc = exc_value
if rich:
if rich and _is_rich_traceback_enabled:
rich_tb = Traceback.from_exception(
type(exc),
exc,
Expand Down