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

SwissDataScienceCenter / renku-python / 4182482698

pending completion
4182482698

Pull #3320

github-actions

GitHub
Merge 26747a6e8 into 0e5906373
Pull Request #3320: chore: fix coveralls comments

20751 of 28900 relevant lines covered (71.8%)

2.47 hits per line

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

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

20
GitHub
21
~~~~~~
22

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

26
.. code-block:: text
27

28
    Ahhhhhhhh! You have found a bug. 🐞
29

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

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

36
Sentry
37
~~~~~~
38

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

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

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

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

62
import click
6✔
63

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

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

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

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

83

84
class RenkuExceptionsHandler(click.Group):
6✔
85
    """Handles all RenkuExceptions."""
86

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

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

111

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

115
    REPO_URL = "https://github.com/SwissDataScienceCenter/renku-python"
6✔
116

117
    ISSUE_SUFFIX = "/issues/new"
6✔
118

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

123
        if HAS_SENTRY:
6✔
124
            import sentry_sdk
×
125

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

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

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

141
            self._handle_github()
×
142

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

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

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

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

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

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

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

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

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

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

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

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

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

215
    def _process_ignore(self):
6✔
216
        """Print original exception in a console."""
217
        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