• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SwissDataScienceCenter / renku-python / 4651204881

pending completion
4651204881

push

github-actions

GitHub
chore: separate shaky tests (#3381)

3 of 4 new or added lines in 2 files covered. (75.0%)

378 existing lines in 31 files now uncovered.

25407 of 29796 relevant lines covered (85.27%)

9.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.74
/renku/ui/cli/exception_handler.py
1
#
2
# Copyright 2020-2023 - Swiss Data Science Center (SDSC)
3
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4
# Eidgenössische Technische Hochschule Zürich (ETHZ).
5
#
6
# Licensed under the Apache License, Version 2.0 (the "License");
7
# you may not use this file except in compliance with the License.
8
# You may obtain a copy of the License at
9
#
10
#     http://www.apache.org/licenses/LICENSE-2.0
11
#
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS,
14
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
# See the License for the specific language governing permissions and
16
# limitations under the License.
17
"""Renku is not bug-free and you can help us to find them.
22✔
18

19
GitHub
20
~~~~~~
21

22
You can quickly open an issue on GitHub with a traceback and minimal system
23
information when you hit an unhandled exception in the CLI.
24

25
.. code-block:: text
26

27
    Ahhhhhhhh! You have found a bug. 🐞
28

29
    1. Open an issue by typing "open";
30
    2. Print human-readable information by typing "print";
31
    3. See the full traceback without submitting details (default: "ignore").
32

33
    Please select an action by typing its name (open, print, ignore) [ignore]:
34

35
Sentry
36
~~~~~~
37

38
When using ``renku`` as a hosted service the Sentry integration can be enabled
39
to help developers iterate faster by showing them where bugs happen, how often,
40
and who is affected.
41

42
1. Install ``Sentry-SDK`` with ``python -m pip install sentry-sdk``;
43
2. Set environment variables ``SENTRY_DSN=true`` and
44
   ``SENTRY_DSN=https://<key>@sentry.<domain>/<project>``.
45
3. Set the environment variable ``SENTRY_SAMPLE_RATE=0.2``. This would track
46
   20% of all requests in Sentry performance monitoring. Set to 0 to disable.
47

48
.. warning:: User information might be sent to help resolving the problem.
49
   If you are not using your own Sentry instance you should inform users
50
   that you are sending possibly sensitive information to a 3rd-party service.
51
"""
52

53
import os
22✔
54
import platform
22✔
55
import re
22✔
56
import sys
22✔
57
import textwrap
22✔
58
import traceback
22✔
59
from urllib.parse import urlencode
22✔
60

61
import click
22✔
62

63
import renku.ui.cli.utils.color as color
22✔
64
from renku.command.util import ERROR
22✔
65
from renku.core import errors
22✔
66
from renku.ui.service.config import SENTRY_ENABLED, SENTRY_SAMPLERATE
22✔
67

68
_BUG = click.style("Ahhhhhhhh! You have found a bug. 🐞\n\n", fg=color.RED, bold=True)
22✔
69
HAS_SENTRY = SENTRY_ENABLED
22✔
70

71
if SENTRY_ENABLED:
22✔
72
    try:
×
73
        from importlib.metadata import PackageNotFoundError, distribution
×
74
    except ImportError:
×
75
        from importlib_metadata import PackageNotFoundError, distribution  # type: ignore
×
76

77
    try:
×
78
        distribution("sentry-sdk")
×
79
    except PackageNotFoundError:
×
80
        HAS_SENTRY = False
×
81

82

83
class RenkuExceptionsHandler(click.Group):
22✔
84
    """Handles all RenkuExceptions."""
85

86
    def main(self, *args, **kwargs):
22✔
87
        """Catch and print all Renku exceptions."""
88
        from renku.core.errors import MigrationRequired, ParameterError, ProjectNotSupported, RenkuException, UsageError
20✔
89

90
        try:
20✔
91
            return super().main(*args, **kwargs)
20✔
92
        except errors.LockError:
20✔
93
            click.echo(
4✔
94
                click.style("Unable to acquire lock.\n", fg=color.RED)
95
                + "Hint: Please wait for another renku process to finish and then try again."
96
            )
97
        except RenkuException as e:
20✔
98
            click.echo(ERROR + str(e), err=True)
12✔
99
            if e.__cause__ is not None:
12✔
UNCOV
100
                click.echo(f"\n{traceback.format_exc()}")
×
101
            exit_code = 1
12✔
102
            if isinstance(e, (ParameterError, UsageError)):
12✔
103
                exit_code = 2
12✔
104
            elif isinstance(e, MigrationRequired):
12✔
105
                exit_code = 3
4✔
106
            elif isinstance(e, ProjectNotSupported):
12✔
107
                exit_code = 4
4✔
108
            sys.exit(exit_code)
12✔
109

110

111
class IssueFromTraceback(RenkuExceptionsHandler):
22✔
112
    """Create an issue with formatted exception."""
113

114
    REPO_URL = "https://github.com/SwissDataScienceCenter/renku-python"
22✔
115

116
    ISSUE_SUFFIX = "/issues/new"
22✔
117

118
    def __init__(self, *args, **kwargs):
22✔
119
        """Initialize a Sentry client."""
120
        super().__init__(*args, **kwargs)
22✔
121

122
        if HAS_SENTRY:
22✔
123
            import sentry_sdk
×
124

125
            sentry_sdk.init(
×
126
                dsn=os.getenv("SENTRY_DSN"), environment=os.getenv("SENTRY_ENV"), traces_sample_rate=SENTRY_SAMPLERATE
127
            )
128

129
    def main(self, *args, **kwargs):
22✔
130
        """Catch all exceptions."""
131
        try:
20✔
132
            return super().main(*args, **kwargs)
20✔
133
        except Exception:
20✔
UNCOV
134
            if HAS_SENTRY:
×
135
                self._handle_sentry()
×
136

UNCOV
137
            if not (sys.stdin.isatty() and sys.stdout.isatty()):
×
UNCOV
138
                raise
×
139

140
            self._handle_github()
×
141

142
    def _handle_sentry(self):
22✔
143
        """Handle exceptions using Sentry."""
144
        from sentry_sdk import capture_exception, configure_scope
×
145
        from sentry_sdk.utils import capture_internal_exceptions
×
146

147
        with configure_scope() as scope:
×
148
            with capture_internal_exceptions():
×
149
                from renku.core.util.git import get_git_repository
×
150

151
                user = get_git_repository().get_user()
×
152

153
                scope.user = {"name": user.name, "email": user.email}
×
154

155
            event_id = capture_exception()
×
156
            click.echo(_BUG + f"Recorded in Sentry with ID: {event_id}\n", err=True)
×
157
            raise
×
158

159
    def _handle_github(self):
22✔
160
        """Handle exception and submit it as GitHub issue."""
161
        value = click.prompt(
×
162
            _BUG
163
            + click.style('1. Open an issue by typing "open";\n', fg=color.GREEN)
164
            + click.style("2. Print human-readable information by typing " '"print";\n', fg=color.YELLOW)
165
            + click.style(
166
                "3. See the full traceback without submitting details " '(default: "ignore").\n\n', fg=color.RED
167
            )
168
            + "Please select an action by typing its name",
169
            type=click.Choice(["open", "print", "ignore"]),
170
            default="ignore",
171
        )
172
        getattr(self, "_process_" + value)()
×
173

174
    def _format_issue_title(self):
22✔
175
        """Return formatted title."""
176
        return textwrap.shorten("cli: renku " + " ".join(sys.argv[1:]), width=50)
×
177

178
    def _format_issue_body(self, limit=-5):
22✔
179
        """Return formatted body."""
180
        from renku import __version__
×
181

182
        re_paths = r"(" + r"|".join([path or os.getcwd() for path in sys.path]) + r")"
×
183
        tb = re.sub(re_paths, "[...]", traceback.format_exc(limit=limit))
×
184

185
        return (
×
186
            "## Describe the bug\nA clear and concise description.\n\n"
187
            "## Details\n"
188
            "*Please verify and redact the details.*\n\n"
189
            "**Renku version:** " + __version__ + "\n"
190
            "**OS:** " + platform.system() + " (" + platform.version() + ")\n"
191
            "**Python:** " + platform.python_version() + "\n\n"
192
            "### Traceback\n\n```\n" + tb + "```\n\n"
193
            "## Additional context\nAdd any other context about the problem."
194
        )
195

196
    def _format_issue_url(self):
22✔
197
        """Format full issue URL."""
198
        query = urlencode({"title": self._format_issue_title(), "body": self._format_issue_body()})
×
199
        return self.REPO_URL + self.ISSUE_SUFFIX + "?" + query
×
200

201
    def _process_open(self):
22✔
202
        """Open link in a browser."""
203
        click.launch(self._format_issue_url())
×
204
        if not click.confirm("Did it work?", default=True):
×
205
            click.echo()
×
206
            self._process_print()
×
207
            click.secho("\nOpen the line manually and copy the text above\n", fg=color.YELLOW)
×
208
            click.secho("  " + self.REPO_URL + self.ISSUE_SUFFIX + "\n", bold=True)
×
209

210
    def _process_print(self):
22✔
211
        """Print link in a console."""
212
        click.echo(self._format_issue_body(limit=None))
×
213

214
    def _process_ignore(self):
22✔
215
        """Print original exception in a console."""
216
        raise
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc