Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-typer for openSUSE:Factory 
checked in at 2026-10-21 20:51:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-typer (Old)
 and      /work/SRC/openSUSE:Factory/.python-typer.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-typer"

Wed Oct 21 20:51:27 2026 rev:40 rq:1386404 version:0.27.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-typer/python-typer.changes        
2026-09-10 11:47:02.368622891 +0200
+++ /work/SRC/openSUSE:Factory/.python-typer.new.2252680/python-typer.changes   
2026-10-21 20:51:30.544932544 +0200
@@ -1,0 +2,6 @@
+Mon Oct 19 05:38:49 UTC 2026 - Matthias Bach <[email protected]>
+
+- Update to 0.27.3
+  - Escape terminal control characters in error messages.
+
+-------------------------------------------------------------------

Old:
----
  typer-0.27.2.tar.gz

New:
----
  typer-0.27.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-typer.spec ++++++
--- /var/tmp/diff_new_pack.XzkP2K/_old  2026-10-21 20:51:31.673979618 +0200
+++ /var/tmp/diff_new_pack.XzkP2K/_new  2026-10-21 20:51:31.675979702 +0200
@@ -25,7 +25,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-typer
-Version:        0.27.2
+Version:        0.27.3
 Release:        0
 Summary:        Typer, build great CLIs. Easy to code. Based on Python type 
hints
 License:        MIT

++++++ set-proper-pythonpath-for-file-argument-tests.patch ++++++
--- /var/tmp/diff_new_pack.XzkP2K/_old  2026-10-21 20:51:31.711981203 +0200
+++ /var/tmp/diff_new_pack.XzkP2K/_new  2026-10-21 20:51:31.718981495 +0200
@@ -9,7 +9,7 @@
  1 file changed, 14 insertions(+)
 
 diff --git a/tests/test_types_file.py b/tests/test_types_file.py
-index 3fb7d82..734867d 100644
+index 49dfcd6..77658c3 100644
 --- a/tests/test_types_file.py
 +++ b/tests/test_types_file.py
 @@ -1,3 +1,4 @@
@@ -22,7 +22,7 @@
  from typer.testing import CliRunner
  
 +import tests
- from tests.utils import needs_linux, needs_windows
+ from tests.utils import needs_linux, needs_macos, needs_windows
  
  app = typer.Typer()
 @@ -136,6 +138,17 @@ def test_binary_dash() -> None:

++++++ set-proper-pythonpath-for-tutorial-script-tests.patch ++++++
--- /var/tmp/diff_new_pack.XzkP2K/_old  2026-10-21 20:51:31.736982245 +0200
+++ /var/tmp/diff_new_pack.XzkP2K/_new  2026-10-21 20:51:31.741982454 +0200
@@ -62,33 +62,40 @@
 [  123s] tests/test_tutorial/test_subcommands/test_tutorial003.py:146: 
AssertionError
 
 ---
- tests/test_tutorial/test_subcommands/test_tutorial001.py | 2 +-
- tests/test_tutorial/test_subcommands/test_tutorial003.py | 2 +-
- 2 files changed, 2 insertions(+), 2 deletions(-)
+ .../test_subcommands/test_tutorial001.py           |  5 ++++-
+ .../test_subcommands/test_tutorial003.py           |  5 ++++-
+ tests/test_types_file.py                           | 14 ++++++++++++++
+ 3 files changed, 22 insertions(+), 2 deletions(-)
 
 diff --git a/tests/test_tutorial/test_subcommands/test_tutorial001.py 
b/tests/test_tutorial/test_subcommands/test_tutorial001.py
-index 4e8b23e..cbeca19 100644
+index 4e8b23e..0b40ab7 100644
 --- a/tests/test_tutorial/test_subcommands/test_tutorial001.py
 +++ b/tests/test_tutorial/test_subcommands/test_tutorial001.py
-@@ -87,7 +87,7 @@ def test_scripts(mod):
+@@ -87,7 +87,10 @@ def test_scripts(mod):
      from docs_src.subcommands.tutorial001_py310 import items, users
  
      env = os.environ.copy()
 -    env["PYTHONPATH"] = ":".join(list(tutorial001_py310.__path__))
-+    env["PYTHONPATH"] = ":".join(list(tutorial001_py310.__path__) + 
[env["PYTHONPATH"]] if "PYTHONPATH" in env else [])
++    env["PYTHONPATH"] = ":".join(
++        list(tutorial001_py310.__path__)
++        + ([env["PYTHONPATH"]] if "PYTHONPATH" in env else [])
++    )
  
      for module in [mod, items, users]:
          result = subprocess.run(
 diff --git a/tests/test_tutorial/test_subcommands/test_tutorial003.py 
b/tests/test_tutorial/test_subcommands/test_tutorial003.py
-index 30b4352..6217aed 100644
+index 30b4352..756e582 100644
 --- a/tests/test_tutorial/test_subcommands/test_tutorial003.py
 +++ b/tests/test_tutorial/test_subcommands/test_tutorial003.py
-@@ -166,7 +166,7 @@ def test_scripts(mod):
+@@ -166,7 +166,10 @@ def test_scripts(mod):
      )
  
      env = os.environ.copy()
 -    env["PYTHONPATH"] = ":".join(list(tutorial003_py310.__path__))
-+    env["PYTHONPATH"] = ":".join(list(tutorial003_py310.__path__) + 
[env["PYTHONPATH"]] if "PYTHONPATH" in env else [])
++    env["PYTHONPATH"] = ":".join(
++        list(tutorial003_py310.__path__)
++        + ([env["PYTHONPATH"]] if "PYTHONPATH" in env else [])
++    )
  
      for module in [mod, items, lands, reigns, towns, users]:
          result = subprocess.run(

++++++ typer-0.27.2.tar.gz -> typer-0.27.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/PKG-INFO new/typer-0.27.3/PKG-INFO
--- old/typer-0.27.2/PKG-INFO   1970-01-01 01:00:00.000000000 +0100
+++ new/typer-0.27.3/PKG-INFO   1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +1,8 @@
 Metadata-Version: 2.4
 Name: typer
-Version: 0.27.2
+Version: 0.27.3
 Summary: Typer, build great CLIs. Easy to code. Based on Python type hints.
-Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <[email protected]>
+Author-Email: Sebastián Ramírez <[email protected]>
 License-Expression: MIT
 License-File: LICENSE
 Classifier: Intended Audience :: Information Technology
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/pyproject.toml 
new/typer-0.27.3/pyproject.toml
--- old/typer-0.27.2/pyproject.toml     2026-08-28 12:26:52.310655600 +0200
+++ new/typer-0.27.3/pyproject.toml     2026-10-06 19:24:13.366423800 +0200
@@ -43,7 +43,7 @@
     "colorama; platform_system == 'Windows'",
 ]
 readme = "README.md"
-version = "0.27.2"
+version = "0.27.3"
 
 [project.urls]
 Homepage = "https://github.com/fastapi/typer";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/tests/conftest.py 
new/typer-0.27.3/tests/conftest.py
--- old/typer-0.27.2/tests/conftest.py  1970-01-01 01:00:00.000000000 +0100
+++ new/typer-0.27.3/tests/conftest.py  2026-10-06 19:24:08.230129000 +0200
@@ -0,0 +1,10 @@
+from pathlib import Path
+
+import pytest
+import typer
+
+
[email protected](name="app_dir")
+def patch_app_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
+    monkeypatch.setattr(typer, "get_app_dir", lambda _app_name: str(tmp_path))
+    return tmp_path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/typer-0.27.2/tests/test_terminal_control_characters.py 
new/typer-0.27.3/tests/test_terminal_control_characters.py
--- old/typer-0.27.2/tests/test_terminal_control_characters.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/typer-0.27.3/tests/test_terminal_control_characters.py  2026-10-06 
19:24:08.231451000 +0200
@@ -0,0 +1,118 @@
+import re
+from typing import Annotated
+
+import pytest
+import typer
+from typer._click.exceptions import NoSuchOption
+from typer.testing import CliRunner
+
+runner = CliRunner()
+
+
[email protected](params=["rich", None], ids=["rich", "plain"])
+def app(request: pytest.FixtureRequest) -> typer.Typer:
+    app = typer.Typer(add_completion=False, rich_markup_mode=request.param)
+
+    @app.command()
+    def no_args() -> None:
+        pass  # pragma: no cover
+
+    @app.command()
+    def read_file(value: typer.FileText) -> None:
+        pass  # pragma: no cover
+
+    @app.command()
+    def parse(
+        value: Annotated[int, typer.Argument(parser=lambda x: int(x, 0))],
+    ) -> None:
+        pass  # pragma: no cover
+
+    return app
+
+
[email protected]("color", [False, True])
[email protected](
+    ("command", "prefix", "message"),
+    [
+        ("no-args", "", "Got unexpected extra argument(s)"),
+        ("no-args", "--", "No such option"),
+        ("read-file", "", "Invalid value"),
+        ("parse", "", "Invalid value"),
+    ],
+    ids=["extra-argument", "unknown-option", "file-open", "custom-parser"],
+)
[email protected](
+    ("value", "escaped"),
+    [
+        ("\x1b[2J\x1b[H", r"\x1b[2J\x1b[H"),
+        ("\x1b]52;c;YWJj\x07", r"\x1b]52;c;YWJj\x07"),
+        ("\x1b]52;c;YWJj\x1b\\", "\\x1b]52;c;YWJj\\x1b\\"),
+        ("\x9b2J\x9d52;c;YWJj\x9c", r"\x9b2J\x9d52;c;YWJj\x9c"),
+        ("\r\n\t\b\x7f", r"\x0d\x0a\x09\x08\x7f"),
+        (
+            
"text-\u00e9-\u65e5\u672c-\U0001f469\u200d\U0001f4bb-'quoted'-\\text",
+            
"text-\u00e9-\u65e5\u672c-\U0001f469\u200d\U0001f4bb-'quoted'-\\text",
+        ),
+    ],
+    ids=["screen", "osc-bel", "osc-st", "c1", "whitespace-controls", 
"unicode"],
+)
+def test_error_values(
+    app: typer.Typer,
+    command: str,
+    prefix: str,
+    message: str,
+    value: str,
+    escaped: str,
+    color: bool,
+    monkeypatch: pytest.MonkeyPatch,
+) -> None:
+    from typer import rich_utils
+
+    monkeypatch.delenv("NO_COLOR", raising=False)
+    monkeypatch.setenv("TERM", "xterm-256color")
+    monkeypatch.setattr(rich_utils, "FORCE_TERMINAL", color)
+    monkeypatch.setattr(rich_utils, "MAX_WIDTH", 3000)
+    result = runner.invoke(app, [command, prefix + "input-" + value], 
color=color)
+
+    assert result.exit_code == 2
+    # Remove only generated color styles so other terminal controls cannot 
hide.
+    output = re.sub(r"\x1b\[[0-9;]*m", "", result.stderr)
+    assert message in output
+    assert prefix + "input-" + escaped in output
+    assert re.search(r"[\x00-\x09\x0b-\x1f\x7f-\x9f]", output) is None
+
+
+def test_unknown_option_preserves_original_value() -> None:
+    value = "--input-\x00\x1f\x7f\x80\x9f"
+    error = NoSuchOption(value)
+
+    assert error.option_name == value
+    assert error.format_message() == r"No such option: 
--input-\x00\x1f\x7f\x80\x9f"
+
+
+def test_argument_preserves_original_value() -> None:
+    app = typer.Typer(add_completion=False)
+    value = "input-\x1b[2J\x1b]52;c;YWJj\x07"
+
+    @app.command()
+    def main(value: str) -> None:
+        typer.echo(repr(value))
+
+    result = runner.invoke(app, [value])
+
+    assert result.exit_code == 0
+    assert result.stdout == repr(value) + "\n"
+
+
+def test_echo_preserves_terminal_sequences() -> None:
+    app = typer.Typer(add_completion=False)
+    value = "\x1b[2J\x1b]52;c;YWJj\x07"
+
+    @app.command()
+    def main() -> None:
+        typer.echo(value, nl=False)
+
+    result = runner.invoke(app, color=True)
+
+    assert result.exit_code == 0
+    assert result.stdout_bytes == value.encode()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/typer-0.27.2/tests/test_tutorial/test_app_dir/test_tutorial001.py 
new/typer-0.27.3/tests/test_tutorial/test_app_dir/test_tutorial001.py
--- old/typer-0.27.2/tests/test_tutorial/test_app_dir/test_tutorial001.py       
2026-08-28 12:26:49.028846500 +0200
+++ new/typer-0.27.3/tests/test_tutorial/test_app_dir/test_tutorial001.py       
2026-10-06 19:24:08.232470500 +0200
@@ -2,8 +2,6 @@
 import sys
 from pathlib import Path
 
-import pytest
-import typer
 from typer.testing import CliRunner
 
 from docs_src.app_dir import tutorial001_py310 as mod
@@ -11,26 +9,14 @@
 runner = CliRunner()
 
 
[email protected](name="config_file")
-def create_config_file():
-    app_dir = Path(typer.get_app_dir("my-super-cli-app"))
-    app_dir.mkdir(parents=True, exist_ok=True)
-    config_path = app_dir / "config.json"
-    config_path.touch(exist_ok=True)
-
-    yield config_path
-
-    config_path.unlink()
-    app_dir.rmdir()
-
-
-def test_cli_config_doesnt_exist():
+def test_cli_config_doesnt_exist(app_dir: Path):
     result = runner.invoke(mod.app)
     assert result.exit_code == 0
     assert "Config file doesn't exist yet" in result.output
 
 
-def test_cli_config_exists(config_file: Path):
+def test_cli_config_exists(app_dir: Path):
+    (app_dir / "config.json").touch()
     result = runner.invoke(mod.app)
     assert result.exit_code == 0
     assert "Config file doesn't exist yet" not in result.output
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/typer-0.27.2/tests/test_tutorial/test_launch/test_tutorial002.py 
new/typer-0.27.3/tests/test_tutorial/test_launch/test_tutorial002.py
--- old/typer-0.27.2/tests/test_tutorial/test_launch/test_tutorial002.py        
2026-08-28 12:26:49.030032900 +0200
+++ new/typer-0.27.3/tests/test_tutorial/test_launch/test_tutorial002.py        
2026-10-06 19:24:08.234306800 +0200
@@ -3,8 +3,6 @@
 from pathlib import Path
 from unittest.mock import patch
 
-import pytest
-import typer
 from typer.testing import CliRunner
 
 from docs_src.launch import tutorial002_py310 as mod
@@ -12,23 +10,6 @@
 runner = CliRunner()
 
 
[email protected](name="app_dir")
-def app_dir():
-    app_dir = Path(typer.get_app_dir("my-super-cli-app"))
-    if app_dir.exists():  # pragma: no cover
-        for item in app_dir.iterdir():
-            if item.is_file():
-                item.unlink()
-
-    yield app_dir
-
-    if app_dir.exists():
-        for item in app_dir.iterdir():
-            if item.is_file():
-                item.unlink()
-        app_dir.rmdir()
-
-
 def test_cli(app_dir: Path):
     with patch("typer.launch") as launch_mock:
         result = runner.invoke(mod.app)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/tests/test_types_file.py 
new/typer-0.27.3/tests/test_types_file.py
--- old/typer-0.27.2/tests/test_types_file.py   2026-08-28 12:26:49.031862000 
+0200
+++ new/typer-0.27.3/tests/test_types_file.py   2026-10-06 19:24:08.237492300 
+0200
@@ -9,7 +9,7 @@
 from typer._click.utils import PacifyFlushWrapper
 from typer.testing import CliRunner
 
-from tests.utils import needs_linux, needs_windows
+from tests.utils import needs_linux, needs_macos, needs_windows
 
 app = typer.Typer()
 
@@ -302,6 +302,33 @@
     assert typer.get_app_dir("My App", force_posix=True) == 
"/home/tester/.my-app"
 
 
+@needs_linux
+def test_app_dir_linux_xdg_config_home(monkeypatch: pytest.MonkeyPatch) -> 
None:
+    monkeypatch.setenv("XDG_CONFIG_HOME", "/home/tester/config")
+
+    assert typer.get_app_dir("My App") == "/home/tester/config/my-app"
+
+
+@needs_linux
+def test_app_dir_linux_default(monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
+    monkeypatch.setattr("os.path.expanduser", lambda _path: 
"/home/tester/.config")
+
+    assert typer.get_app_dir("My App") == "/home/tester/.config/my-app"
+
+
+@needs_macos
+def test_app_dir_macos(monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.setattr(
+        "os.path.expanduser", lambda _path: "/Users/tester/Library/Application 
Support"
+    )
+
+    assert (
+        typer.get_app_dir("My App")
+        == "/Users/tester/Library/Application Support/My App"
+    )
+
+
 def test_text_stream_binary_buffer(monkeypatch) -> None:
     class TextStdinWithBinaryBuffer:
         def __init__(self, data: bytes) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/.agents/skills/typer/SKILL.md 
new/typer-0.27.3/typer/.agents/skills/typer/SKILL.md
--- old/typer-0.27.2/typer/.agents/skills/typer/SKILL.md        2026-08-28 
12:26:49.032167700 +0200
+++ new/typer-0.27.3/typer/.agents/skills/typer/SKILL.md        2026-10-06 
19:24:08.237807500 +0200
@@ -106,6 +106,7 @@
 ```python
 # DO NOT DO THIS: old style. Use Annotated instead.
 
+
 @app.command()
 def main(name: str = typer.Argument(default="World")):
     # Note that name is an optional Argument, as a default is provided
@@ -117,6 +118,7 @@
 ```python
 # DO NOT DO THIS: old style. Use Annotated without a default value instead.
 
+
 @app.command()
 def main(name: str = typer.Argument(default=...)):
     # Note that name is now a required Argument
@@ -269,12 +271,16 @@
 
 app = typer.Typer(rich_markup_mode="markdown")
 
+
 @app.command(help="**Delete** a user with *username*.")
 def delete(
-    username: Annotated[str, typer.Argument(help="The username to be 
**deleted** :boom:")]
+    username: Annotated[
+        str, typer.Argument(help="The username to be **deleted** :boom:")
+    ],
 ):
     print(f"Deleting user: {username}")
 
+
 if __name__ == "__main__":
     app()
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/__init__.py 
new/typer-0.27.3/typer/__init__.py
--- old/typer-0.27.2/typer/__init__.py  2026-08-28 12:26:49.032167700 +0200
+++ new/typer-0.27.3/typer/__init__.py  2026-10-06 19:24:08.237807500 +0200
@@ -1,6 +1,6 @@
 """Typer, build great CLIs. Easy to code. Based on Python type hints."""
 
-__version__ = "0.27.2"
+__version__ = "0.27.3"
 
 from shutil import get_terminal_size as get_terminal_size
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/_click/_compat.py 
new/typer-0.27.3/typer/_click/_compat.py
--- old/typer-0.27.2/typer/_click/_compat.py    2026-08-28 12:26:49.032277300 
+0200
+++ new/typer-0.27.3/typer/_click/_compat.py    2026-10-06 19:24:08.237968000 
+0200
@@ -495,7 +495,7 @@
                 ansi_wrapper.reset_all()
                 raise
 
-        rv.write = _safe_write  # type: ignore[method-assign]  # ty: 
ignore[invalid-assignment]
+        rv.write = _safe_write  # type: ignore[method-assign]
 
         try:
             _ansi_stream_wrappers[stream] = rv
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/_click/core.py 
new/typer-0.27.3/typer/_click/core.py
--- old/typer-0.27.2/typer/_click/core.py       2026-08-28 12:26:49.032277300 
+0200
+++ new/typer-0.27.3/typer/_click/core.py       2026-10-06 19:24:08.237968000 
+0200
@@ -23,7 +23,7 @@
 from .globals import pop_context, push_context
 from .parser import _OptionParser
 from .termui import style
-from .utils import echo, make_default_short_help
+from .utils import _escape_control_characters, echo, make_default_short_help
 
 if TYPE_CHECKING:
     from ..core import TyperOption
@@ -719,7 +719,8 @@
             _, args = param.handle_parse_result(ctx, opts, args)
 
         if args and not ctx.allow_extra_args and not ctx.resilient_parsing:
-            ctx.fail(f"Got unexpected extra argument(s) ({' '.join(map(str, 
args))})")
+            extra_args = " ".join(_escape_control_characters(str(arg)) for arg 
in args)
+            ctx.fail(f"Got unexpected extra argument(s) ({extra_args})")
 
         ctx.args = args
         ctx._opt_prefixes.update(parser._opt_prefixes)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/_click/exceptions.py 
new/typer-0.27.3/typer/_click/exceptions.py
--- old/typer-0.27.2/typer/_click/exceptions.py 2026-08-28 12:26:49.032277300 
+0200
+++ new/typer-0.27.3/typer/_click/exceptions.py 2026-10-06 19:24:08.237968000 
+0200
@@ -4,7 +4,7 @@
 from ..exceptions import TyperException
 from ._compat import get_text_stderr
 from .globals import resolve_color_default
-from .utils import echo, format_filename
+from .utils import _escape_control_characters, echo, format_filename
 
 if TYPE_CHECKING:
     from .core import Command, Context, Parameter
@@ -178,7 +178,7 @@
         ctx: Union["Context", None] = None,
     ) -> None:
         if message is None:
-            message = f"No such option: {option_name}"
+            message = f"No such option: 
{_escape_control_characters(option_name)}"
 
         super().__init__(message, ctx)
         self.option_name = option_name
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/_click/types.py 
new/typer-0.27.3/typer/_click/types.py
--- old/typer-0.27.2/typer/_click/types.py      2026-08-28 12:26:49.032450000 
+0200
+++ new/typer-0.27.3/typer/_click/types.py      2026-10-06 19:24:08.237968000 
+0200
@@ -18,7 +18,7 @@
 
 from ._compat import _get_argv_encoding, open_stream
 from .exceptions import BadParameter
-from .utils import LazyFile, format_filename, safecall
+from .utils import LazyFile, _escape_control_characters, format_filename, 
safecall
 
 if TYPE_CHECKING:
     from .core import Context, Parameter
@@ -138,7 +138,7 @@
                 assert isinstance(value, bytes)
                 value = value.decode("utf-8", "replace")
 
-            self.fail(value, param, ctx)
+            self.fail(_escape_control_characters(value), param, ctx)
 
 
 class StringParamType(ParamType):
@@ -559,8 +559,9 @@
                     ctx.call_on_close(safecall(f.flush))
 
             return f
-        except OSError as e:  # pragma: no cover
-            self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
+        except OSError as e:
+            filename = _escape_control_characters(format_filename(value))
+            self.fail(f"'{filename}': {e.strerror}", param, ctx)
 
     def shell_complete(
         self, ctx: "Context", param: "Parameter", incomplete: str
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typer-0.27.2/typer/_click/utils.py 
new/typer-0.27.3/typer/_click/utils.py
--- old/typer-0.27.2/typer/_click/utils.py      2026-08-28 12:26:49.032450000 
+0200
+++ new/typer-0.27.3/typer/_click/utils.py      2026-10-06 19:24:08.237968000 
+0200
@@ -34,6 +34,13 @@
 R = TypeVar("R")
 
 
+def _escape_control_characters(value: str) -> str:
+    """Display untrusted diagnostic values without terminal control 
characters."""
+    return re.sub(
+        r"[\x00-\x1f\x7f-\x9f]", lambda match: f"\\x{ord(match[0]):02x}", value
+    )
+
+
 def _posixify(name: str) -> str:
     return "-".join(name.split()).lower()
 

Reply via email to