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()
