From ee072d8ccf1c70166d162ca7873d1e6fa3ffa495 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Thu, 7 Nov 2024 20:59:54 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9C=A8=20Update=20`launch`=20to=20not=20?= =?UTF-8?q?print=20anything=20when=20opening=20urls=20(#1035)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_launch.py | 51 +++++++++++++++++++++++++++++++++ typer/__init__.py | 2 +- typer/main.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 tests/test_launch.py diff --git a/tests/test_launch.py b/tests/test_launch.py new file mode 100644 index 0000000000..75aaa2f091 --- /dev/null +++ b/tests/test_launch.py @@ -0,0 +1,51 @@ +import subprocess +from unittest.mock import patch + +import pytest +import typer + +url = "http://example.com" + + +@pytest.mark.parametrize( + "system, command", + [ + ("Darwin", "open"), + ("Linux", "xdg-open"), + ("FreeBSD", "xdg-open"), + ], +) +def test_launch_url_unix(system: str, command: str): + with patch("platform.system", return_value=system), patch( + "shutil.which", return_value=True + ), patch("subprocess.Popen") as mock_popen: + typer.launch(url) + + mock_popen.assert_called_once_with( + [command, url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ) + + +def test_launch_url_windows(): + with patch("platform.system", return_value="Windows"), patch( + "webbrowser.open" + ) as mock_webbrowser_open: + typer.launch(url) + + mock_webbrowser_open.assert_called_once_with(url) + + +def test_launch_url_no_xdg_open(): + with patch("platform.system", return_value="Linux"), patch( + "shutil.which", return_value=None + ), patch("webbrowser.open") as mock_webbrowser_open: + typer.launch(url) + + mock_webbrowser_open.assert_called_once_with(url) + + +def test_calls_original_launch_when_not_passing_urls(): + with patch("typer.main.click.launch", return_value=0) as launch_mock: + typer.launch("not a url") + + launch_mock.assert_called_once_with("not a url") diff --git a/typer/__init__.py b/typer/__init__.py index b422dd00d3..3d84062a76 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -12,7 +12,6 @@ from click.termui import echo_via_pager as echo_via_pager from click.termui import edit as edit from click.termui import getchar as getchar -from click.termui import launch as launch from click.termui import pause as pause from click.termui import progressbar as progressbar from click.termui import prompt as prompt @@ -28,6 +27,7 @@ from . import colors as colors from .main import Typer as Typer +from .main import launch as launch from .main import run as run from .models import CallbackParam as CallbackParam from .models import Context as Context diff --git a/typer/main.py b/typer/main.py index e12cec0f99..55d865c780 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1,5 +1,8 @@ import inspect import os +import platform +import shutil +import subprocess import sys import traceback from datetime import datetime @@ -1078,3 +1081,68 @@ def run(function: Callable[..., Any]) -> None: app = Typer(add_completion=False) app.command()(function) app() + + +def _is_macos() -> bool: + return platform.system() == "Darwin" + + +def _is_linux_or_bsd() -> bool: + if platform.system() == "Linux": + return True + + return "BSD" in platform.system() + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + This function handles url in different operating systems separately: + - On macOS (Darwin), it uses the 'open' command. + - On Linux and BSD, it uses 'xdg-open' if available. + - On Windows (and other OSes), it uses the standard webbrowser module. + + The function avoids, when possible, using the webbrowser module on Linux and macOS + to prevent spammy terminal messages from some browsers (e.g., Chrome). + + Examples:: + + typer.launch("https://typer.tiangolo.com/") + typer.launch("/my/downloaded/file", locate=True) + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + + if url.startswith("http://") or url.startswith("https://"): + if _is_macos(): + return subprocess.Popen( + ["open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ).wait() + + has_xdg_open = _is_linux_or_bsd() and shutil.which("xdg-open") is not None + + if has_xdg_open: + return subprocess.Popen( + ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ).wait() + + import webbrowser + + webbrowser.open(url) + + return 0 + + else: + return click.launch(url) From e31fc5733aaf0d088dcaca55d773130e144a78e3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Nov 2024 21:04:39 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index a441e20303..6b466af6f2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Update `launch` to not print anything when opening urls. PR [#1035](https://github.com/fastapi/typer/pull/1035) by [@patrick91](https://github.com/patrick91). * ✨ Show help items in order of definition. PR [#944](https://github.com/fastapi/typer/pull/944) by [@svlandeg](https://github.com/svlandeg). ### Fixes From c5233c0c18211fd9c8c90de202b124e6fcb96e67 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Thu, 7 Nov 2024 21:06:56 +0000 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9C=A8=20Handle=20`KeyboardInterrupt`=20?= =?UTF-8?q?separately=20from=20other=20exceptions=20(#1039)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- tests/test_exit_errors.py | 13 +++++++++++++ typer/core.py | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_exit_errors.py b/tests/test_exit_errors.py index 598308b1d1..1fdc3e36b0 100644 --- a/tests/test_exit_errors.py +++ b/tests/test_exit_errors.py @@ -19,6 +19,19 @@ def main(): assert result.exit_code == 1 +def test_keyboardinterrupt(): + # Mainly for coverage/completeness + app = typer.Typer() + + @app.command() + def main(): + raise KeyboardInterrupt() + + result = runner.invoke(app) + assert result.exit_code == 130 + assert result.stdout == "" + + def test_oserror(): # Mainly for coverage/completeness app = typer.Typer() diff --git a/typer/core.py b/typer/core.py index 84a151a0f1..d9d651aaf8 100644 --- a/typer/core.py +++ b/typer/core.py @@ -205,9 +205,11 @@ def _main( # even always obvious that `rv` indicates success/failure # by its truthiness/falsiness ctx.exit() - except (EOFError, KeyboardInterrupt) as e: + except EOFError as e: click.echo(file=sys.stderr) raise click.Abort() from e + except KeyboardInterrupt as e: + raise click.exceptions.Exit(130) from e except click.ClickException as e: if not standalone_mode: raise From e80ba57e44ba0b4b2a22092d0c6e0d8fcbbacc53 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Nov 2024 21:10:07 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 6b466af6f2..9a0434b12e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ✨ Handle `KeyboardInterrupt` separately from other exceptions. PR [#1039](https://github.com/fastapi/typer/pull/1039) by [@patrick91](https://github.com/patrick91). * ✨ Update `launch` to not print anything when opening urls. PR [#1035](https://github.com/fastapi/typer/pull/1035) by [@patrick91](https://github.com/patrick91). * ✨ Show help items in order of definition. PR [#944](https://github.com/fastapi/typer/pull/944) by [@svlandeg](https://github.com/svlandeg). From 7bf84ab4bcbf0fc262fb076b7a41c648be0b545f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 7 Nov 2024 21:10:58 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.13.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 2 ++ typer/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 9a0434b12e..65878bd35e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,8 @@ ## Latest Changes +## 0.13.0 + ### Features * ✨ Handle `KeyboardInterrupt` separately from other exceptions. PR [#1039](https://github.com/fastapi/typer/pull/1039) by [@patrick91](https://github.com/patrick91). diff --git a/typer/__init__.py b/typer/__init__.py index 3d84062a76..0ba5475321 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -1,6 +1,6 @@ """Typer, build great CLIs. Easy to code. Based on Python type hints.""" -__version__ = "0.12.5" +__version__ = "0.13.0" from shutil import get_terminal_size as get_terminal_size From f74d172f4abbdeb54831242234bb169e659b930b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:38:02 +0100 Subject: [PATCH 6/9] =?UTF-8?q?=E2=AC=86=20Update=20pytest-cov=20requireme?= =?UTF-8?q?nt=20from=20<6.0.0,>=3D2.10.0=20to=20>=3D2.10.0,<7.0.0=20(#1033?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.10.0...v6.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-tests.txt b/requirements-tests.txt index fbac3312c1..38f9e3b1b2 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,7 +1,7 @@ -e . pytest >=4.4.0,<9.0.0 -pytest-cov >=2.10.0,<6.0.0 +pytest-cov >=2.10.0,<7.0.0 coverage[toml] >=6.2,<8.0 pytest-xdist >=1.32.0,<4.0.0 pytest-sugar >=0.9.4,<1.1.0 From f7371bd5a48c57503b085088d97504c9c4ca16ab Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Nov 2024 09:38:25 +0000 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 65878bd35e..44c203410e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Internal + +* ⬆ Update pytest-cov requirement from <6.0.0,>=2.10.0 to >=2.10.0,<7.0.0. PR [#1033](https://github.com/fastapi/typer/pull/1033) by [@dependabot[bot]](https://github.com/apps/dependabot). + ## 0.13.0 ### Features From 55f30638c07d85ad2e5ababb80c3d8abee28f802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:49:26 +0100 Subject: [PATCH 8/9] =?UTF-8?q?=E2=AC=86=20Bump=20tiangolo/latest-changes?= =?UTF-8?q?=20from=200.3.1=20to=200.3.2=20(#1044)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tiangolo/latest-changes](https://github.com/tiangolo/latest-changes) from 0.3.1 to 0.3.2. - [Release notes](https://github.com/tiangolo/latest-changes/releases) - [Commits](https://github.com/tiangolo/latest-changes/compare/0.3.1...0.3.2) --- updated-dependencies: - dependency-name: tiangolo/latest-changes dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/latest-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 6a3ac059a5..4a3e4e15eb 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -34,7 +34,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: tiangolo/latest-changes@0.3.1 + - uses: tiangolo/latest-changes@0.3.2 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/release-notes.md From 299ad704aa2f4ea26e7660dc53a667d4170bfa7c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 8 Nov 2024 13:49:47 +0000 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 44c203410e..96f79e8d32 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Internal +* ⬆ Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#1044](https://github.com/fastapi/typer/pull/1044) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Update pytest-cov requirement from <6.0.0,>=2.10.0 to >=2.10.0,<7.0.0. PR [#1033](https://github.com/fastapi/typer/pull/1033) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.13.0