Skip to content

Commit 661b49e

Browse files
nathanjmcdougallpre-commit-ci[bot]svlandeg
authored
✅ Use Ruff rules to ensure safe lazy-loading of rich (#1297)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sofie Van Landeghem <svlandeg@users.noreply.github.com>
1 parent 0fc0dda commit 661b49e

24 files changed

+85
-102
lines changed

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,20 @@ select = [
164164
"B", # flake8-bugbear
165165
"C4", # flake8-comprehensions
166166
"UP", # pyupgrade
167+
"TID", # flake8-tidy-imports
167168
]
168169
ignore = [
169170
"E501", # line too long, handled by black
170171
"B008", # do not perform function calls in argument defaults
171172
"C901", # too complex
172173
"W191", # indentation contains tabs
174+
"TID252", # relative imports okay
173175
]
174176

175177
[tool.ruff.lint.per-file-ignores]
176178
# "__init__.py" = ["F401"]
179+
# rich_utils is allowed to use rich imports
180+
"typer/rich_utils.py" = ["TID251"]
177181
# This file is more readable without yield from
178182
"docs_src/progressbar/tutorial004.py" = ["UP028", "B007"]
179183
# Default mutable data structure
@@ -192,6 +196,8 @@ ignore = [
192196
"docs_src/prompt/tutorial003.py" = ["F841"]
193197
# Loop control variable `x` not used within loop body
194198
"docs_src/using_click/tutorial001.py" = ["B007"]
199+
# No need to worry about rich imports in docs
200+
"docs_src/*" = ["TID"]
195201

196202
[tool.ruff.lint.isort]
197203
known-third-party = ["typer", "click"]
@@ -201,3 +207,10 @@ known-first-party = ["reigns", "towns", "lands", "items", "users"]
201207
[tool.ruff.lint.pyupgrade]
202208
# Preserve types, even if a file imports `from __future__ import annotations`.
203209
keep-runtime-typing = true
210+
211+
[tool.ruff.lint.flake8-tidy-imports]
212+
# Import rich_utils from within functions (lazy), not at the module level (TID253)
213+
banned-module-level-imports = ["typer.rich_utils"]
214+
215+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
216+
"rich".msg = "Use 'typer.rich_utils' instead of importing from 'rich' directly."

tests/assets/type_error_no_rich.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typer
22
import typer.main
33

4-
typer.main.rich = None
4+
typer.main.HAS_RICH = False
55

66

77
def main(name: str = "morty"):

tests/assets/type_error_no_rich_short_disable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typer
22
import typer.main
33

4-
typer.main.rich = None
4+
typer.main.HAS_RICH = False
55

66

77
app = typer.Typer(pretty_exceptions_short=False)

tests/test_corner_cases.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pytest
12
import typer.core
23
from typer.testing import CliRunner
34

@@ -16,17 +17,16 @@ def test_hidden_option():
1617
assert "(dynamic)" in result.output
1718

1819

19-
def test_hidden_option_no_rich():
20-
rich = typer.core.rich
21-
typer.core.rich = None
20+
def test_hidden_option_no_rich(monkeypatch: pytest.MonkeyPatch):
21+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
22+
2223
result = runner.invoke(mod.app, ["--help"])
2324
assert result.exit_code == 0
2425
assert "Say hello" in result.output
2526
assert "--name" not in result.output
2627
assert "/lastname" in result.output
2728
assert "TEST_LASTNAME" in result.output
2829
assert "(dynamic)" in result.output
29-
typer.core.rich = rich
3030

3131

3232
def test_coverage_call():

tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33

4+
import pytest
45
import typer
56
import typer.core
67
from typer.testing import CliRunner
@@ -22,16 +23,14 @@ def test_help():
2223
assert "default: World" in result.output
2324

2425

25-
def test_help_no_rich():
26-
rich = typer.core.rich
27-
typer.core.rich = None
26+
def test_help_no_rich(monkeypatch: pytest.MonkeyPatch):
27+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
2828
result = runner.invoke(app, ["--help"])
2929
assert result.exit_code == 0
3030
assert "[OPTIONS] [NAME]" in result.output
3131
assert "Arguments" in result.output
3232
assert "env var: AWESOME_NAME" in result.output
3333
assert "default: World" in result.output
34-
typer.core.rich = rich
3534

3635

3736
def test_call_arg():

tests/test_tutorial/test_arguments/test_envvar/test_tutorial001_an.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33

4+
import pytest
45
import typer
56
import typer.core
67
from typer.testing import CliRunner
@@ -22,16 +23,15 @@ def test_help():
2223
assert "default: World" in result.output
2324

2425

25-
def test_help_no_rich():
26-
rich = typer.core.rich
27-
typer.core.rich = None
26+
def test_help_no_rich(monkeypatch: pytest.MonkeyPatch):
27+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
28+
2829
result = runner.invoke(app, ["--help"])
2930
assert result.exit_code == 0
3031
assert "[OPTIONS] [NAME]" in result.output
3132
assert "Arguments" in result.output
3233
assert "env var: AWESOME_NAME" in result.output
3334
assert "default: World" in result.output
34-
typer.core.rich = rich
3535

3636

3737
def test_call_arg():

tests/test_tutorial/test_arguments/test_help/test_tutorial001.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33

4+
import pytest
45
import typer
56
import typer.core
67
from typer.testing import CliRunner
@@ -23,17 +24,15 @@ def test_help():
2324
assert "[required]" in result.output
2425

2526

26-
def test_help_no_rich():
27-
rich = typer.core.rich
28-
typer.core.rich = None
27+
def test_help_no_rich(monkeypatch: pytest.MonkeyPatch):
28+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
2929
result = runner.invoke(app, ["--help"])
3030
assert result.exit_code == 0
3131
assert "[OPTIONS] NAME" in result.output
3232
assert "Arguments" in result.output
3333
assert "NAME" in result.output
3434
assert "The name of the user to greet" in result.output
3535
assert "[required]" in result.output
36-
typer.core.rich = rich
3736

3837

3938
def test_call_arg():

tests/test_tutorial/test_arguments/test_help/test_tutorial001_an.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33

4+
import pytest
45
import typer
56
import typer.core
67
from typer.testing import CliRunner
@@ -23,17 +24,15 @@ def test_help():
2324
assert "[required]" in result.output
2425

2526

26-
def test_help_no_rich():
27-
rich = typer.core.rich
28-
typer.core.rich = None
27+
def test_help_no_rich(monkeypatch: pytest.MonkeyPatch):
28+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
2929
result = runner.invoke(app, ["--help"])
3030
assert result.exit_code == 0
3131
assert "[OPTIONS] NAME" in result.output
3232
assert "Arguments" in result.output
3333
assert "NAME" in result.output
3434
assert "The name of the user to greet" in result.output
3535
assert "[required]" in result.output
36-
typer.core.rich = rich
3736

3837

3938
def test_call_arg():

tests/test_tutorial/test_arguments/test_help/test_tutorial008.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33

4+
import pytest
45
import typer
56
import typer.core
67
from typer.testing import CliRunner
@@ -22,16 +23,14 @@ def test_help():
2223
assert "[default: World]" not in result.output
2324

2425

25-
def test_help_no_rich():
26-
rich = typer.core.rich
27-
typer.core.rich = None
26+
def test_help_no_rich(monkeypatch: pytest.MonkeyPatch):
27+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
2828
result = runner.invoke(app, ["--help"])
2929
assert result.exit_code == 0
3030
assert "[OPTIONS] [NAME]" in result.output
3131
assert "Say hi to NAME very gently, like Dirk." in result.output
3232
assert "Arguments" not in result.output
3333
assert "[default: World]" not in result.output
34-
typer.core.rich = rich
3534

3635

3736
def test_call_arg():

tests/test_tutorial/test_arguments/test_help/test_tutorial008_an.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import sys
33

4+
import pytest
45
import typer
56
import typer.core
67
from typer.testing import CliRunner
@@ -22,16 +23,14 @@ def test_help():
2223
assert "[default: World]" not in result.output
2324

2425

25-
def test_help_no_rich():
26-
rich = typer.core.rich
27-
typer.core.rich = None
26+
def test_help_no_rich(monkeypatch: pytest.MonkeyPatch):
27+
monkeypatch.setattr(typer.core, "HAS_RICH", False)
2828
result = runner.invoke(app, ["--help"])
2929
assert result.exit_code == 0
3030
assert "[OPTIONS] [NAME]" in result.output
3131
assert "Say hi to NAME very gently, like Dirk." in result.output
3232
assert "Arguments" not in result.output
3333
assert "[default: World]" not in result.output
34-
typer.core.rich = rich
3534

3635

3736
def test_call_arg():

0 commit comments

Comments
 (0)