23 | It looks like we have not found a solution for this error. You can request to display a solution for this
24 | error if you want by clicking the button below.
25 |
64 |
65 |
66 |
120 |
--------------------------------------------------------------------------------
/src/exceptionite/assets/components/tabs/index.js:
--------------------------------------------------------------------------------
1 | import BasicTab from "./Basic.vue"
2 | import DumpsTab from "./Dumps.vue"
3 |
4 | export {
5 | BasicTab,
6 | DumpsTab,
7 | }
--------------------------------------------------------------------------------
/src/exceptionite/assets/editorUrl.js:
--------------------------------------------------------------------------------
1 | export default function editorUrl(editorName, file, lineNumber) {
2 | const editor = editorName
3 | const editors = {
4 | sublime: 'subl://open?url=file://%path&line=%line',
5 | textmate: 'txmt://open?url=file://%path&line=%line',
6 | emacs: 'emacs://open?url=file://%path&line=%line',
7 | macvim: 'mvim://open/?url=file://%path&line=%line',
8 | pycharm: 'pycharm://open?file=%path&line=%line',
9 | idea: 'idea://open?file=%path&line=%line',
10 | vscode: 'vscode://file/%path:%line',
11 | 'vscode-insiders': 'vscode-insiders://file/%path:%line',
12 | 'vscode-remote': 'vscode://vscode-remote/%path:%line',
13 | 'vscode-insiders-remote': 'vscode-insiders://vscode-remote/%path:%line',
14 | vscodium: 'vscodium://file/%path:%line',
15 | atom: 'atom://core/open/file?filename=%path&line=%line',
16 | };
17 |
18 | if (!Object.keys(editors).includes(editor)) {
19 | console.error(
20 | `'${editor}' is not supported. Support editors are: ${Object.keys(editors).join(', ')}`,
21 | );
22 |
23 | return null;
24 | }
25 |
26 | return editors[editor]
27 | .replace('%path', encodeURIComponent(file))
28 | .replace('%line', encodeURIComponent(lineNumber));
29 | }
--------------------------------------------------------------------------------
/src/exceptionite/assets/themes/atom-one-dark.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Atom One Dark by Daniel Gamage
4 | Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
5 |
6 | base: #282c34
7 | mono-1: #abb2bf
8 | mono-2: #818896
9 | mono-3: #5c6370
10 | hue-1: #56b6c2
11 | hue-2: #61aeee
12 | hue-3: #c678dd
13 | hue-4: #98c379
14 | hue-5: #e06c75
15 | hue-5-2: #be5046
16 | hue-6: #d19a66
17 | hue-6-2: #e6c07b
18 |
19 | */
20 |
21 | .dark .hljs {
22 | color: #abb2bf;
23 | /* background: #282c34; to match general page theme bg-gray-800 instead*/
24 | }
25 |
26 | .dark .hljs-comment,
27 | .dark .hljs-quote {
28 | color: #5c6370;
29 | font-style: italic;
30 | }
31 |
32 | .dark .hljs-doctag,
33 | .dark .hljs-keyword,
34 | .dark .hljs-formula {
35 | color: #c678dd;
36 | }
37 |
38 | .dark .hljs-section,
39 | .dark .hljs-name,
40 | .dark .hljs-selector-tag,
41 | .dark .hljs-deletion,
42 | .dark .hljs-subst {
43 | color: #e06c75;
44 | }
45 |
46 | .dark .hljs-literal {
47 | color: #56b6c2;
48 | }
49 |
50 | .dark .hljs-string,
51 | .dark .hljs-regexp,
52 | .dark .hljs-addition,
53 | .dark .hljs-attribute,
54 | .dark .hljs-meta .hljs-string {
55 | color: #98c379;
56 | }
57 |
58 | .dark .hljs-attr,
59 | .dark .hljs-variable,
60 | .dark .hljs-template-variable,
61 | .dark .hljs-type,
62 | .dark .hljs-selector-class,
63 | .dark .hljs-selector-attr,
64 | .dark .hljs-selector-pseudo,
65 | .dark .hljs-number {
66 | color: #d19a66;
67 | }
68 |
69 | .dark .hljs-symbol,
70 | .dark .hljs-bullet,
71 | .dark .hljs-link,
72 | .dark .hljs-meta,
73 | .dark .hljs-selector-id,
74 | .dark .hljs-title {
75 | color: #61aeee;
76 | }
77 |
78 | .dark .hljs-built_in,
79 | .dark .hljs-title.class_,
80 | .dark .hljs-class .hljs-title {
81 | color: #e6c07b;
82 | }
83 |
84 | .dark .hljs-emphasis {
85 | font-style: italic;
86 | }
87 |
88 | .dark .hljs-strong {
89 | font-weight: bold;
90 | }
91 |
92 | .dark .hljs-link {
93 | text-decoration: underline;
94 | }
95 |
--------------------------------------------------------------------------------
/src/exceptionite/assets/themes/atom-one-light.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Atom One Light by Daniel Gamage
4 | Original One Light Syntax theme from https://github.com/atom/one-light-syntax
5 |
6 | base: #fafafa
7 | mono-1: #383a42
8 | mono-2: #686b77
9 | mono-3: #a0a1a7
10 | hue-1: #0184bb
11 | hue-2: #4078f2
12 | hue-3: #a626a4
13 | hue-4: #50a14f
14 | hue-5: #e45649
15 | hue-5-2: #c91243
16 | hue-6: #986801
17 | hue-6-2: #c18401
18 |
19 | */
20 |
21 | .hljs {
22 | color: #383a42;
23 | background: #fafafa;
24 | }
25 |
26 | .hljs-comment,
27 | .hljs-quote {
28 | color: #a0a1a7;
29 | font-style: italic;
30 | }
31 |
32 | .hljs-doctag,
33 | .hljs-keyword,
34 | .hljs-formula {
35 | color: #a626a4;
36 | }
37 |
38 | .hljs-section,
39 | .hljs-name,
40 | .hljs-selector-tag,
41 | .hljs-deletion,
42 | .hljs-subst {
43 | color: #e45649;
44 | }
45 |
46 | .hljs-literal {
47 | color: #0184bb;
48 | }
49 |
50 | .hljs-string,
51 | .hljs-regexp,
52 | .hljs-addition,
53 | .hljs-attribute,
54 | .hljs-meta .hljs-string {
55 | color: #50a14f;
56 | }
57 |
58 | .hljs-attr,
59 | .hljs-variable,
60 | .hljs-template-variable,
61 | .hljs-type,
62 | .hljs-selector-class,
63 | .hljs-selector-attr,
64 | .hljs-selector-pseudo,
65 | .hljs-number {
66 | color: #986801;
67 | }
68 |
69 | .hljs-symbol,
70 | .hljs-bullet,
71 | .hljs-link,
72 | .hljs-meta,
73 | .hljs-selector-id,
74 | .hljs-title {
75 | color: #4078f2;
76 | }
77 |
78 | .hljs-built_in,
79 | .hljs-title.class_,
80 | .hljs-class .hljs-title {
81 | color: #c18401;
82 | }
83 |
84 | .hljs-emphasis {
85 | font-style: italic;
86 | }
87 |
88 | .hljs-strong {
89 | font-weight: bold;
90 | }
91 |
92 | .hljs-link {
93 | text-decoration: underline;
94 | }
95 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/Environment.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import platform
3 | import socket
4 | import os
5 |
6 | from ..Block import Block
7 |
8 |
9 | class Environment(Block):
10 | id = "environment"
11 | name = "System Environment"
12 | icon = "TerminalIcon"
13 |
14 | def build(self):
15 | python_version = (
16 | f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
17 | )
18 | default_encoding = sys.getdefaultencoding()
19 | file_system_encoding = sys.getfilesystemencoding()
20 | os_name = platform.system()
21 | if os_name == "Darwin":
22 | os_name = "macOS"
23 |
24 | # when VPN is enabled it can fails for some VPN clients on macOS
25 | try:
26 | ip = socket.gethostbyname(socket.gethostname())
27 | except socket.gaierror:
28 | print(
29 | "Exceptionite did not manage to fetch the IP address. Disable you VPN or add "
30 | + "'127.0.0.1 YOUR_HOSTNAME' line in /etc/hosts file."
31 | )
32 | ip = "Error fetching the IP address (open your terminal)"
33 |
34 | return {
35 | "Python Version": python_version,
36 | "Python Interpreter": sys.executable,
37 | "Virtual env": os.getenv("VIRTUAL_ENV"),
38 | "Python argv": sys.argv,
39 | "Working Dir": os.getcwd(),
40 | "OS": os_name,
41 | "Arch": platform.architecture()[0],
42 | "Host Name": socket.gethostname(),
43 | "IP": ip,
44 | "File System Encoding": file_system_encoding,
45 | "Default Encoding": default_encoding,
46 | }
47 |
48 | def has_content(self):
49 | return True
50 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/Git.py:
--------------------------------------------------------------------------------
1 | import shlex
2 | import subprocess
3 |
4 | from ..Block import Block
5 |
6 |
7 | class Git(Block):
8 | id = "git"
9 | name = "Git"
10 | icon = "ShareIcon"
11 | disable_scrubbing = True
12 |
13 | def build(self):
14 | git_version = subprocess.check_output(shlex.split("git --version")).strip()
15 | try:
16 | commit = subprocess.check_output(
17 | shlex.split("git rev-parse HEAD"), stderr=subprocess.STDOUT
18 | ).strip()
19 | except subprocess.CalledProcessError:
20 | # not a git repository
21 | return {
22 | "commit": "",
23 | "branch": "",
24 | "git_version": git_version.decode("utf-8"),
25 | "remote": "",
26 | }
27 | branch = subprocess.check_output(
28 | shlex.split("git rev-parse --abbrev-ref HEAD"), stderr=subprocess.STDOUT
29 | ).strip()
30 | try:
31 | remote = subprocess.check_output(
32 | shlex.split("git config --get remote.origin.url"), stderr=subprocess.STDOUT
33 | ).strip()
34 | except subprocess.CalledProcessError:
35 | remote = b""
36 |
37 | return {
38 | "commit": commit.decode("utf-8"),
39 | "branch": branch.decode("utf-8"),
40 | "git_version": git_version.decode("utf-8"),
41 | "remote": remote.decode("utf-8"),
42 | }
43 |
44 | def has_content(self):
45 | return True
46 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/Packages.py:
--------------------------------------------------------------------------------
1 | import pkg_resources
2 |
3 | from ..Block import Block
4 |
5 |
6 | class Packages(Block):
7 | id = "packages"
8 | name = "Installed Packages"
9 | icon = "PuzzleIcon"
10 | disable_scrubbing = True
11 |
12 | def build(self):
13 | packages = {}
14 | for package in pkg_resources.working_set:
15 | packages.update({package.key: package.version})
16 | return packages
17 |
18 | def has_content(self):
19 | return True
20 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/PackagesUpdates.py:
--------------------------------------------------------------------------------
1 | import pkg_resources
2 | import requests
3 |
4 | from ..Block import Block
5 |
6 |
7 | def get_latest_version(name):
8 | r = requests.get(f"https://pypi.org/pypi/{name}/json")
9 | if r.status_code == 200:
10 | version = r.json()["info"]["version"]
11 | return version
12 | return None
13 |
14 |
15 | class PackagesUpdates(Block):
16 | id = "packages_updates"
17 | name = "Packages to update"
18 | icon = "ArrowCircleUpIcon"
19 | component = "PackagesUpdatesBlock"
20 | empty_msg = "Selected packages are up to date !"
21 | disable_scrubbing = True
22 |
23 | def build(self):
24 | installed_packages = {
25 | package.key: package.version for package in pkg_resources.working_set
26 | }
27 |
28 | packages_to_check = self.options.get("list", ["exceptionite"])
29 | packages = {}
30 | if packages_to_check:
31 | for package_name in packages_to_check:
32 | current_version = installed_packages.get(package_name)
33 | latest_version = get_latest_version(package_name)
34 | if current_version != latest_version:
35 | packages.update(
36 | {
37 | package_name: {
38 | "current": installed_packages.get(package_name),
39 | "latest": latest_version,
40 | }
41 | }
42 | )
43 |
44 | return packages
45 |
46 | def has_content(self):
47 | return len(self.data.keys()) > 0
48 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/PossibleSolutions.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import urllib.parse
4 |
5 | from ..Block import Block
6 | from .. import solutions
7 |
8 |
9 | class PossibleSolutions(Block):
10 |
11 | id = "possible_solutions"
12 | name = "Possible Solutions"
13 | component = "PossibleSolutionsBlock"
14 | advertise_content = True
15 | disable_scrubbing = True
16 | empty_msg = "No solution found for this error."
17 | icon = "LightBulbIcon"
18 |
19 | def __init__(self, tab, handler, options):
20 | super().__init__(tab, handler, options)
21 | self.registered_solutions = []
22 | self.register(
23 | *solutions.PythonSolutions.get(),
24 | )
25 |
26 | def build(self):
27 | possible_solutions = []
28 | for solution in self.registered_solutions:
29 | r = re.compile(solution.regex())
30 | doc_link = None
31 | if r.search(self.handler.message()):
32 | description = solution.description()
33 | title = solution.title()
34 | matches = [m.groupdict() for m in r.finditer(self.handler.message())]
35 | if hasattr(solution, "documentation_link"):
36 | if solution.documentation_link():
37 | doc_link = solution.documentation_link()
38 | for code, replacement in matches[0].items():
39 | description = description.replace(":" + code, replacement)
40 | title = title.replace(":" + code, replacement)
41 |
42 | possible_solutions.append(
43 | {"title": title, "description": description, "doc_link": doc_link}
44 | )
45 |
46 | # build request_link
47 | request_link = ""
48 | if not possible_solutions:
49 | request_link = self.get_request_link()
50 |
51 | return {
52 | "first": possible_solutions[0] if len(possible_solutions) > 0 else None,
53 | "solutions": possible_solutions,
54 | "request_link": request_link,
55 | }
56 |
57 | def get_request_link(self):
58 | params = {
59 | "title": f"Add exceptionite solution for `{self.handler.exception()}`",
60 | "body": f"A solution is missing:\nException namespace: `{self.handler.namespace()}`\nError message:\n```\n{self.handler.message()}\n```", # noqa: E501
61 | "labels": "solution-request",
62 | }
63 | return f"https://github.com/MasoniteFramework/exceptionite/issues/new/?{urllib.parse.urlencode(params)}" # noqa: E501
64 |
65 | def register(self, *solutions):
66 | self.registered_solutions += solutions
67 | return self
68 |
69 | def has_content(self):
70 | return len(self.data.get("solutions")) > 0
71 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/StackOverflow.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import urllib
3 | from ..Block import Block
4 |
5 |
6 | class StackOverflow(Block):
7 | id = "stackoverflow"
8 | name = "Stack Overflow Answers"
9 | component = "StackOverflowBlock"
10 | tags = ["python"]
11 | api_url = "https://api.stackexchange.com/2.2/"
12 | empty_msg = "No solution found on Stack Overflow."
13 | disable_scrubbing = True
14 |
15 | def get_tags(self):
16 | return ";".join(self.tags)
17 |
18 | def build(self):
19 | query = urllib.parse.urlencode(
20 | {
21 | "order": "asc",
22 | "sort": "relevance",
23 | "q": self.handler.message(),
24 | "body": self.handler.message(),
25 | "accepted": True,
26 | "tagged": self.get_tags(),
27 | "site": "stackoverflow",
28 | }
29 | )
30 |
31 | try:
32 | response = requests.get(f"{self.api_url}search/advanced?{query}").json()
33 | except requests.exceptions.ConnectionError:
34 | return {}
35 |
36 | accepted_answer_ids = []
37 |
38 | if not response.get("items"):
39 | query = urllib.parse.urlencode(
40 | {
41 | "order": "desc",
42 | "sort": "relevance",
43 | "intitle": self.handler.exception(),
44 | "site": "stackoverflow",
45 | "filter": "!-*jbN-(0_ynL",
46 | "tagged": self.get_tags(),
47 | "key": "k7C3UwXDt3J0xOpri8RPgA((",
48 | }
49 | )
50 | response = requests.get(f"{self.api_url}search/advanced?{query}").json()
51 |
52 | for question in response.get("items", []):
53 | if "accepted_answer_id" in question:
54 | accepted_answer_ids.append(str(question["accepted_answer_id"]))
55 |
56 | query = urllib.parse.urlencode(
57 | {"order": "desc", "sort": "activity", "site": "stackoverflow"}
58 | )
59 | answers = requests.get(
60 | f"{self.api_url}answers/{';'.join(accepted_answer_ids)}?{query}"
61 | ).json()
62 |
63 | return {"questions": response["items"], "answers": answers}
64 |
65 | def has_content(self):
66 | return len(self.data.get("questions", [])) > 0
67 |
--------------------------------------------------------------------------------
/src/exceptionite/blocks/__init__.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa: E501
2 | from .Environment import Environment
3 | from .Git import Git
4 | from .Packages import Packages
5 | from .PackagesUpdates import PackagesUpdates
6 | from .PossibleSolutions import PossibleSolutions
7 | from .StackOverflow import StackOverflow
8 |
--------------------------------------------------------------------------------
/src/exceptionite/django/ExceptioniteReporter.py:
--------------------------------------------------------------------------------
1 | from .. import Handler, Block
2 | from .options import OPTIONS
3 | from ..solutions import DjangoSolutions
4 |
5 |
6 | class ContextBlock(Block):
7 | id = "django-request"
8 | name = "Context"
9 | icon = "DesktopComputerIcon"
10 | has_sections = True
11 |
12 | def build(self):
13 | from django.utils.version import get_version
14 |
15 | return {
16 | "Django": {
17 | "Version": get_version(),
18 | },
19 | "Request Info": {
20 | "Path": self.handler.request.path,
21 | "GET": self.handler.request.GET,
22 | "POST": self.handler.request.POST,
23 | "Files": self.handler.request.FILES,
24 | "Cookies": self.handler.request.COOKIES,
25 | "Request Method": self.handler.request.method,
26 | },
27 | }
28 |
29 |
30 | class ExceptioniteReporter:
31 | def __init__(self, request, exc_type, exc_value, tb):
32 | self.request = request
33 | self.exception = exc_value
34 |
35 | def get_traceback_html(self):
36 | from django.http.response import HttpResponse
37 |
38 | handler = Handler()
39 | handler.start(self.exception)
40 | handler.render("terminal")
41 | handler.request = self.request
42 | handler.renderer("web").tab("context").add_blocks(ContextBlock)
43 | handler.renderer("web").tab("solutions").block("possible_solutions").register(
44 | *DjangoSolutions().get()
45 | )
46 | handler.set_options(OPTIONS)
47 | return HttpResponse(handler.render("web"))
48 |
49 | def get_traceback_json(self):
50 | from django.http.response import JsonResponse
51 |
52 | handler = Handler()
53 | handler.start(self.exception)
54 | handler.render("terminal")
55 | return JsonResponse(handler.render("json"))
56 |
57 |
58 | class Exceptionite404Reporter:
59 | """Handle Django 404 errors specifically in debug mode."""
60 |
61 | def __init__(self):
62 | from mock import patch
63 | from django.http import HttpResponseNotFound
64 |
65 | patcher = patch(
66 | "django.views.debug.technical_404_response",
67 | lambda request, exception: HttpResponseNotFound(
68 | ExceptioniteReporter(request, None, exception, None).get_traceback_html()
69 | ),
70 | )
71 | patcher.start()
72 |
--------------------------------------------------------------------------------
/src/exceptionite/django/__init__.py:
--------------------------------------------------------------------------------
1 | from .drf_exception_handler import drf_exception_handler
2 | from .ExceptioniteReporter import ExceptioniteReporter, Exceptionite404Reporter
3 |
--------------------------------------------------------------------------------
/src/exceptionite/django/drf_exception_handler.py:
--------------------------------------------------------------------------------
1 | from .ExceptioniteReporter import ExceptioniteReporter
2 |
3 |
4 | def drf_exception_handler(exc, context):
5 | from rest_framework.views import exception_handler
6 |
7 | # Call REST framework's default exception handler first,
8 | # to get the standard error response.
9 | response = exception_handler(exc, context)
10 |
11 | # Now add the HTTP status code to the response.
12 | if response is not None:
13 | response.data["status_code"] = response.status_code
14 |
15 | # Handle exceptions from drf with exceptionite
16 | request = context["request"]
17 | content_type = request.accepted_renderer.media_type
18 | reporter = ExceptioniteReporter(request, None, exc, None)
19 | if content_type == "text/html":
20 | return reporter.get_traceback_html()
21 | elif content_type == "application/json":
22 | return reporter.get_traceback_json()
23 |
24 | return response
25 |
--------------------------------------------------------------------------------
/src/exceptionite/django/options.py:
--------------------------------------------------------------------------------
1 | OPTIONS = {
2 | "options": {
3 | "editor": "vscode",
4 | "search_url": "https://www.google.com/search?q=",
5 | "links": {
6 | "doc": "https://docs.djangoproject.com/en/4.0/",
7 | "repo": "https://github.com/django/django",
8 | },
9 | "stack": {"offset": 8, "shorten": True},
10 | "hide_sensitive_data": True,
11 | },
12 | "handlers": {
13 | "context": True,
14 | "solutions": {"stackoverflow": False, "possible_solutions": True},
15 | "recommendations": {"packages_updates": {"list": ["exceptionite"]}},
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/exceptionite/exceptions.py:
--------------------------------------------------------------------------------
1 | class ConfigurationException(Exception):
2 | pass
3 |
4 |
5 | class ContextParsingException(Exception):
6 | pass
7 |
8 |
9 | class TabNotFound(Exception):
10 | pass
11 |
--------------------------------------------------------------------------------
/src/exceptionite/flask/ExceptioniteReporter.py:
--------------------------------------------------------------------------------
1 | from .. import Handler, Block
2 | from .options import OPTIONS
3 |
4 |
5 | class ContextBlock(Block):
6 | id = "flask"
7 | name = "Flask"
8 | icon = "DesktopComputerIcon"
9 |
10 | def build(self):
11 | return {
12 | "Path": self.handler.request.path,
13 | "Input": dict(self.handler.request.args),
14 | "Request Method": self.handler.request.method,
15 | }
16 |
17 |
18 | class ExceptioniteReporter:
19 | def __init__(self, exception, request):
20 | self.exception = exception
21 | self.request = request
22 | handler = Handler()
23 | handler.renderer("web").tab("context").add_blocks(ContextBlock)
24 | handler.request = self.request
25 | handler.set_options(OPTIONS)
26 |
27 | handler.start(self.exception)
28 | self.handler = handler
29 |
30 | def html(self):
31 | return self.handler.render("web")
32 |
33 | def json(self):
34 | return self.handler.render("json")
35 |
36 | def terminal(self):
37 | return self.handler.render("terminal")
38 |
--------------------------------------------------------------------------------
/src/exceptionite/flask/__init__.py:
--------------------------------------------------------------------------------
1 | from .ExceptioniteReporter import ExceptioniteReporter
2 |
--------------------------------------------------------------------------------
/src/exceptionite/flask/options.py:
--------------------------------------------------------------------------------
1 | OPTIONS = {
2 | "options": {
3 | "editor": "vscode",
4 | "search_url": "https://www.google.com/search?q=",
5 | "links": {
6 | "doc": "https://flask.palletsprojects.com",
7 | "repo": "https://github.com/pallets/flask/",
8 | },
9 | "stack": {"offset": 8, "shorten": True},
10 | "hide_sensitive_data": True,
11 | },
12 | "handlers": {
13 | "context": True,
14 | "solutions": {"stackoverflow": False, "possible_solutions": True},
15 | "recommendations": {"packages_updates": {"list": ["exceptionite", "flask"]}},
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/exceptionite/options.py:
--------------------------------------------------------------------------------
1 | DEFAULT_OPTIONS = {
2 | "options": {
3 | "editor": "vscode",
4 | "search_url": "https://www.google.com/search?q=",
5 | "links": {
6 | "doc": "https://docs.masoniteproject.com",
7 | "repo": "https://github.com/MasoniteFramework/masonite",
8 | },
9 | "stack": {"offset": 8, "shorten": True},
10 | "hide_sensitive_data": True,
11 | },
12 | "handlers": {
13 | "context": True,
14 | "solutions": {"stackoverflow": False, "possible_solutions": True},
15 | "recommendations": {"packages_updates": {"list": ["exceptionite"]}},
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/exceptionite/renderers/JSONRenderer.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING
2 |
3 | if TYPE_CHECKING:
4 | from ..Handler import Handler
5 |
6 |
7 | class JSONRenderer:
8 | """Renderer used to render exception as JSON payload."""
9 |
10 | def __init__(self, handler: "Handler") -> None:
11 | self.handler = handler
12 | self.data: dict = {}
13 |
14 | def build(self):
15 | return {
16 | "exception": {
17 | "type": self.handler.exception(),
18 | "namespace": self.handler.namespace(),
19 | },
20 | "message": self.handler.message(),
21 | "stacktrace": self.handler.stacktrace().reverse().serialize_light(),
22 | }
23 |
24 | def render(self) -> str:
25 | """Render the JSON payload."""
26 | self.data = self.build()
27 | return self.data
28 |
--------------------------------------------------------------------------------
/src/exceptionite/renderers/TerminalRenderer.py:
--------------------------------------------------------------------------------
1 | from colorama import init as init_colorama, Fore, Style
2 | from typing import TYPE_CHECKING
3 |
4 | if TYPE_CHECKING:
5 | from ..Handler import Handler
6 |
7 |
8 | COLORS_PREFIX = {
9 | "error": Fore.RED + Style.BRIGHT,
10 | "info": Fore.CYAN + Style.BRIGHT,
11 | "success": Fore.GREEN + Style.BRIGHT,
12 | }
13 |
14 |
15 | def color_output(color: str, output: str) -> str:
16 | return f"{COLORS_PREFIX.get(color)}{output}" + "\033[0m"
17 |
18 |
19 | def get_frame_line_output(frame, index: int = None) -> str:
20 | frame_method = color_output("info", f"{frame.method}()")
21 | frame_line = f"{frame.relative_file or frame.file}: L{frame.lineno} in {frame_method}"
22 | if index:
23 | frame_index_colored = index if frame.is_vendor else color_output("success", index)
24 | return f"{frame_index_colored} {frame_line}"
25 | return frame_line
26 |
27 |
28 | class TerminalRenderer:
29 | """Renderer used to print an exception nicely in the terminal. It will render stack
30 | trace too."""
31 |
32 | def __init__(self, handler: "Handler") -> None:
33 | self.handler = handler
34 |
35 | def render(self) -> str:
36 | """Print exception and stack trace nicely in terminal."""
37 | init_colorama()
38 |
39 | # start printing exception to terminal
40 | print("")
41 | print("")
42 | colored_exception_type = color_output("error", self.handler.exception())
43 | print(f" {colored_exception_type}: {self.handler.message()}")
44 | print("")
45 |
46 | # show most recent frame of the stack
47 | stacktrace = self.handler.stacktrace().reverse()
48 | first_frame = stacktrace.first()
49 | first_frame_code = first_frame.file_contents
50 | print(f" {get_frame_line_output(first_frame)}")
51 | print("")
52 |
53 | # get highest line number to align numbers with padding
54 | max_no_len = len(str(max(first_frame_code.keys())))
55 | for lineno, code_line in first_frame_code.items():
56 | lineno_str = str(lineno)
57 | if len(lineno_str) < max_no_len:
58 | for space in range(max_no_len - len(lineno_str)):
59 | lineno_str += " "
60 | code_line = code_line.replace(" ", " ")
61 | # highlight line where exception raised
62 | if lineno == first_frame.offending_line:
63 | print(color_output("error", f" > {lineno_str} | {code_line}"))
64 | else:
65 | print(f" {lineno_str} | {code_line}")
66 | print("")
67 |
68 | # show other frames
69 | title = color_output("info", f"Stack Trace ({len(stacktrace)}):")
70 | print(f" {title}")
71 | print("")
72 | for index, frame in enumerate(stacktrace):
73 | frame_index = str(index + 1)
74 | print(f" # {get_frame_line_output(frame, frame_index)}")
75 | return ""
76 |
--------------------------------------------------------------------------------
/src/exceptionite/renderers/WebRenderer.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | import os
3 | from jinja2 import Environment, PackageLoader
4 | from typing import Type, TYPE_CHECKING, List, Callable
5 |
6 |
7 | if TYPE_CHECKING:
8 | from ..Tab import Tab
9 | from ..Handler import Handler
10 | from ..Action import Action
11 |
12 | from ..tabs import ContextTab, SolutionsTab, RecommendationsTab
13 | from ..exceptions import ConfigurationException, TabNotFound
14 | from ..Block import Block
15 |
16 |
17 | class WebRenderer:
18 | """Renderer used to render exception as a beautiful extendable HTML error page which ease
19 | debugging."""
20 |
21 | reserved_tabs = ["context"]
22 |
23 | def __init__(self, handler: "Handler") -> None:
24 | self.handler = handler
25 | # error page interface options
26 | self.tabs: dict = OrderedDict()
27 | self.actions: dict = {}
28 | self.context: dict = {}
29 | # setup base interface
30 | self.add_tabs(ContextTab, SolutionsTab, RecommendationsTab)
31 |
32 | def build(self) -> dict:
33 | """Build the handled exception context to inject into the error page."""
34 | from .. import __version__
35 |
36 | enabled_tabs = self.enabled_tabs()
37 | return {
38 | "config": {
39 | **self.handler.options.to_dict(),
40 | "absolute_path": os.getcwd(),
41 | "version": __version__,
42 | },
43 | "exception": {
44 | "type": self.handler.exception(),
45 | "message": self.handler.message(),
46 | "namespace": self.handler.namespace(),
47 | "stacktrace": self.handler.stacktrace().reverse().serialize(),
48 | },
49 | "tabs": [self.handler.scrub_data(tab.serialize()) for tab in enabled_tabs],
50 | "actions": [action.serialize() for action in self.actions.values()],
51 | }
52 |
53 | def render(self) -> str:
54 | """Render the HTML error page."""
55 | self.data = self.build()
56 | path = os.path.join(
57 | os.path.dirname(os.path.dirname(__file__)), "templates", "exceptionite.js"
58 | )
59 | with open(path, "r", encoding="utf-8") as f:
60 | script = f.read()
61 |
62 | env = Environment(loader=PackageLoader("exceptionite", "templates"))
63 |
64 | template = env.get_template("exception.html")
65 | return template.render({"data": self.data, "script": script})
66 |
67 | def add_tabs(self, *tab_classes: Type["Tab"]) -> "Handler":
68 | """Register a tab in the HTML error page."""
69 | for tab_class in tab_classes:
70 | tab = tab_class(self.handler)
71 | if tab.id in self.reserved_tabs and tab.id in self.tabs:
72 | raise ConfigurationException(
73 | f"exceptionite: {tab.id} is a reserved name. This tab can't be overriden."
74 | )
75 | self.tabs.update({tab.id: tab})
76 | return self
77 |
78 | def add_actions(self, *action_classes: Type["Action"]) -> "Handler":
79 | """Register an action in the HTML error page."""
80 | for action_class in action_classes:
81 | action = action_class(self.handler)
82 | self.actions.update({action.id: action})
83 | return self
84 |
85 | def tab(self, id: str) -> "Tab":
86 | """Get registered tab with the given id."""
87 | try:
88 | return self.tabs[id]
89 | except KeyError:
90 | raise TabNotFound(f"Tab not found: {id}")
91 |
92 | def enabled_tabs(self) -> List["Tab"]:
93 | """Get enabled tabs from options"""
94 | return [
95 | tab
96 | for tab in self.tabs.values()
97 | if self.handler.options.get("handlers").get(tab.id, True)
98 | ]
99 |
100 | def run_action(self, action_id: str, options: dict = {}) -> dict:
101 | """Run the given action with options if any"""
102 | action = self.actions.get(action_id)
103 | return action.run(options)
104 |
105 | def add_context(self, name: str, data: "dict|Callable", icon: str = None) -> "WebRenderer":
106 | """Quick shortcut method to add a context block into 'context' tab."""
107 |
108 | custom_block = Block
109 | custom_block.id = f"context.id.{name.lower()}"
110 | custom_block.name = name
111 | custom_block.icon = icon
112 | if callable(data):
113 | custom_block.build = data
114 | else:
115 | custom_block.build = lambda self: data
116 | self.tab("context").add_blocks(custom_block)
117 | return self
118 |
--------------------------------------------------------------------------------
/src/exceptionite/renderers/__init__.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa: E501
2 | from .WebRenderer import WebRenderer
3 | from .TerminalRenderer import TerminalRenderer
4 | from .JSONRenderer import JSONRenderer
5 |
--------------------------------------------------------------------------------
/src/exceptionite/solutions.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa: E501
2 |
3 |
4 | class PythonSolutions:
5 | @classmethod
6 | def get(cls):
7 | return [
8 | DictionaryUpdateSequence(),
9 | DictionaryUpdateSequenceWithList(),
10 | ClassMethodExists(),
11 | UnexpectedEndBlock(),
12 | QueryDefaultValue(),
13 | NoRelationExistsInDatabase(),
14 | NoColumnExistsOnWhere(),
15 | NoColumnExistsOnWhereSQLite(),
16 | NoColumnExistsOnSelect(),
17 | UnsupportedOperand(),
18 | DivisionByZeroError(),
19 | GetAttributeObject(),
20 | ModuleHasNoAttribute(),
21 | ModuleNotCallable(),
22 | NoModuleNamed(),
23 | Syntax(),
24 | ImportIssue(),
25 | Undefined(),
26 | WrongParameterCount(),
27 | WrongConstructorParameterCount(),
28 | ObjectNotCallable(),
29 | SubscriptableIssue(),
30 | StringIndicesMustBeIntegers(),
31 | MySQLConnectionRefused(),
32 | PostgresConnectionRefused(),
33 | PostgresConnectionFailed(),
34 | DatabaseDoesNotExist(),
35 | DatabaseUserDoesNotExist(),
36 | ]
37 |
38 |
39 | class MasoniteSolutions:
40 | @classmethod
41 | def get(cls):
42 | return [
43 | ClassModelMethodExists(),
44 | ImportIssueWithController(),
45 | IncorrectControllerName(),
46 | IncorrectlyDefinedRoute(),
47 | RouteNameNotFound(),
48 | IncludedTemplateNotFound(),
49 | ContainerKeyNotFoundRegister(),
50 | ContainerKeyNotFoundServiceProvider(),
51 | NotFound404(),
52 | InvalidRouteMethodType(),
53 | ModelNotFound(),
54 | DatabaseDriverNotFound(),
55 | DriverNotFound(),
56 | MethodNotAllowed(),
57 | ]
58 |
59 |
60 | class DjangoSolutions:
61 | @classmethod
62 | def get(cls):
63 | return [
64 | DjangoTemplateNotFound(),
65 | ContextShouldBeList(),
66 | RenderArgumentsOutOfOrder(),
67 | ]
68 |
69 |
70 | class DictionaryUpdateSequence:
71 | def title(self):
72 | return "Updating a dictionary with a set. "
73 |
74 | def description(self):
75 | return (
76 | "Looks like you are trying to update a dictionary but are actually using a set. "
77 | "Double check what you are passing into the update method"
78 | )
79 |
80 | def regex(self):
81 | return r"dictionary update sequence element #0 has length 3; 2 is required"
82 |
83 |
84 | class DictionaryUpdateSequenceWithList:
85 | def title(self):
86 | return "Updating a dictionary with a list or set. "
87 |
88 | def description(self):
89 | return (
90 | "Looks like you are trying to update a dictionary but are actually using a list or a set. "
91 | "Double check what you are passing into the dictionaries update method"
92 | )
93 |
94 | def regex(self):
95 | return r"cannot convert dictionary update sequence element #0 to a sequence"
96 |
97 |
98 | class ClassMethodExists:
99 | def title(self):
100 | return "Check the class method exists"
101 |
102 | def description(self):
103 | return (
104 | "Check the :method attribute exists on the ':class' class. If this is a class you made then check the file its in and see if the method exists."
105 | "If this is a third party class then refer to the documentation."
106 | )
107 |
108 | def regex(self):
109 | return r"^class \'(?P([\w]*))\' has no attribute (?P(\w+))"
110 |
111 |
112 | class ClassModelMethodExists:
113 | def title(self):
114 | return "Model method does not exist"
115 |
116 | def description(self):
117 | return "Could not find the ':method' method on the model class. Please check spelling. If this is a method you expect to be on the builder class then check the ORM documentation"
118 |
119 | def regex(self):
120 | return r"^class model \'(?P([\w]*))\' has no attribute (?P(\w+))"
121 |
122 |
123 | class ImportIssueWithController:
124 | def title(self):
125 | return "Import Error In Controller"
126 |
127 | def description(self):
128 | return (
129 | "The :class controller could not be loaded into the route correctly. Check any recent imports or all imports in the controller. "
130 | "Worst case is you can import the controller directly in the route and you should get a python error."
131 | )
132 |
133 | def regex(self):
134 | return r"named (?P([\w]*)) has been found in"
135 |
136 |
137 | class IncorrectControllerName:
138 | def title(self):
139 | return "Mispelled Controller name"
140 |
141 | def description(self):
142 | return "The :class controller could be mispelled. Check your routes file for :class and make sure that is the correct spelling."
143 |
144 | def regex(self):
145 | return r"named (?P([\w]*)) has been found in"
146 |
147 |
148 | class IncorrectlyDefinedRoute:
149 | def title(self):
150 | return "Check the controller and action is set correctly on the route."
151 |
152 | def description(self):
153 | return "Check the definition on the controller is correct. If using string controllers it should be in the format of 'Controller@action'"
154 |
155 | def regex(self):
156 | return r"named (?P([\w]*)) has been found in"
157 |
158 | def documentation_link(self):
159 | return "https://docs.masoniteproject.com/the-basics/routing#creating-a-route"
160 |
161 |
162 | class RouteNameNotFound:
163 | def title(self):
164 | return "Check the the name exists in your routes file"
165 |
166 | def description(self):
167 | return """Check the routes file and make sure there is a route with the ".name(':name')\" method. You can also run `python craft routes:list` to see a table of routes. Check for your named route in that table."""
168 |
169 | def regex(self):
170 | return r"Could not find route with the name \'(?P([\w]*))\'"
171 |
172 | def documentation_link(self):
173 | return "https://docs.masoniteproject.com/the-basics/routing#name"
174 |
175 |
176 | class IncludedTemplateNotFound:
177 | def title(self):
178 | return "Check any imported templates inside the :name template."
179 |
180 | def description(self):
181 | return (
182 | "The :name template was found but a template included inside the :name template was not found. "
183 | "Check any lines of code that use the extends or include Jinja2 tags inside your template. "
184 | "Check the template path is correct. Included templates are absolute from your template directory and should end with '.html'"
185 | )
186 |
187 | def regex(self):
188 | return (
189 | r"One of the included templates in the \'(?P([\w]*))\' view could not be found"
190 | )
191 |
192 |
193 | class UnexpectedEndBlock:
194 | def title(self):
195 | return "Check the :name was closed correctly."
196 |
197 | def description(self):
198 | return "The error could be difficult to find so check ALL :name tags and make sure the :name tag is opened and closed correctly. "
199 |
200 | def regex(self):
201 | return r"Unexpected end of template. Jinja was looking for the following tags: \'(?P([\w]*))\'."
202 |
203 |
204 | class QueryDefaultValue:
205 | def title(self):
206 | return "Missing default value for ':field'"
207 |
208 | def description(self):
209 | return (
210 | "Default values are typically set on the database level. "
211 | "You can either add a default value on the :field table column in a migration or you should pass a value when creating this record"
212 | )
213 |
214 | def regex(self):
215 | return r"\(1364\, \"Field \'(?P([\w]*))\' doesn't have a default value\"\)"
216 |
217 |
218 | class NoColumnExistsOnWhere:
219 | def title(self):
220 | return "Check the table for the :field column"
221 |
222 | def description(self):
223 | return "Could not find the :field column. Check your 'where' clauses. Is :field on the table you are trying to query? Did you run the migrations yet? Maybe it was not spelled correctly?"
224 |
225 | def regex(self):
226 | return r"Unknown column \'(?P([\w\.]*))\' in \'where clause\'"
227 |
228 |
229 | class NoRelationExistsInDatabase:
230 | def title(self):
231 | return "The table ':table' does not exist in database"
232 |
233 | def description(self):
234 | return "Could not find the table ':table' in the database. Did you run the migrations yet? Maybe the table was not spelled correctly?"
235 |
236 | def regex(self):
237 | return r"relation \"(?P
([\w\.]*))\" does not exist"
238 |
239 |
240 | class NoColumnExistsOnWhereSQLite:
241 | def title(self):
242 | return "Check the table for the :field column"
243 |
244 | def description(self):
245 | return "Could not find the :field column. Is :field on the table you are trying to query? Did you run the migrations yet? Maybe it was not spelled correctly?"
246 |
247 | def regex(self):
248 | return r"no such column: (?P([\w\.]*))"
249 |
250 |
251 | class NoColumnExistsOnSelect:
252 | def title(self):
253 | return "Check the table for the :field column"
254 |
255 | def description(self):
256 | return "Could not find the :field column. Check your 'select' clauses. Is :field on the table you are trying to query? Did you run the migrations yet? Maybe it was not spelled correctly?"
257 |
258 | def regex(self):
259 | return r"Unknown column \'(?P([\w\.]*))\' in \'field list\'"
260 |
261 |
262 | class UnsupportedOperand:
263 | def title(self):
264 | return "Trying to do math for values that are not of the same type (:type1 and :type2)"
265 |
266 | def description(self):
267 | return "Check the type of the 2 types. One is of type :type1 and the the other is of type :type2. They both need to be the same type"
268 |
269 | def regex(self):
270 | return r"unsupported operand type\(s\) for \+\: '(?P([\w\.]*))' and '(?P([\w\.]*))'"
271 |
272 |
273 | class MySQLConnectionRefused:
274 | def title(self):
275 | return "Check database is running and connection details are correct"
276 |
277 | def description(self):
278 | return "Check that MySQL server is running and that MySQL configuration is correct (check that port, hostname, username and password are set correctly, and that environment variables are correctly defined)."
279 |
280 | def regex(self):
281 | return r"Can\'t connect to MySQL server"
282 |
283 |
284 | class PostgresConnectionRefused:
285 | def title(self):
286 | return "Check database is running and connection details are correct"
287 |
288 | def description(self):
289 | return "Check that PostgresSQL server is running and that PostgresSQL configuration is correct (check that port=:port, hostname=:host, username and host are set correctly, and that environment variables are correctly defined)."
290 |
291 | def regex(self):
292 | return r"connection to server at \"(?P([\w\.]*))\", port (?P([\d]*)) failed"
293 |
294 |
295 | class PostgresConnectionFailed:
296 | def title(self):
297 | return "Check database is running and connection details are correct"
298 |
299 | def description(self):
300 | return "Check that PostgresSQL server is running and that PostgresSQL configuration is correct (check that port, hostname, username and host are set correctly, and that environment variables are correctly defined)."
301 |
302 | def regex(self):
303 | return r"connection to server on socket \"(?P(.*))\" failed"
304 |
305 |
306 | class DatabaseDoesNotExist:
307 | def title(self):
308 | return "The database ':database' is not created on the database server"
309 |
310 | def description(self):
311 | return "The application is trying to connect to the database ':database' but it looks like it has not been created."
312 |
313 | def regex(self):
314 | return r"database \"(?P([\w\.]*))\" does not exist"
315 |
316 |
317 | class DatabaseUserDoesNotExist:
318 | def title(self):
319 | return "The database user ':user' is not created on the database server"
320 |
321 | def description(self):
322 | return "The application is trying to connect with the user ':user' but it looks like it has not been created."
323 |
324 | def regex(self):
325 | return r"role \"(?P([\w\.]*))\" does not exist"
326 |
327 |
328 | class DivisionByZeroError:
329 | def title(self):
330 | return "Check variables for any values that could be 0"
331 |
332 | def description(self):
333 | return "Check any place you are doing division. You cannot divide by a zero."
334 |
335 | def regex(self):
336 | return r"division by zero"
337 |
338 |
339 | class ContainerKeyNotFoundRegister:
340 | def title(self):
341 | return "Did you register the key in the service provider or Kernel?"
342 |
343 | def description(self):
344 | return (
345 | "Check the key name was correctly registered in a service provider or the Kernel file"
346 | )
347 |
348 | def regex(self):
349 | return r"key was not found in the container"
350 |
351 |
352 | class ContainerKeyNotFoundServiceProvider:
353 | def title(self):
354 | return "Did you register the service provider?"
355 |
356 | def description(self):
357 | return "If you registered the key in your own service provider, did you register the provider in the config/providers.py file?"
358 |
359 | def regex(self):
360 | return r"key was not found in the container"
361 |
362 | def documentation_link(self):
363 | return "https://docs.masoniteproject.com/architecture/service-providers#registering-the-service-provider"
364 |
365 |
366 | class NotFound404:
367 | def title(self):
368 | return "The '/:route' route could not be found"
369 |
370 | def description(self):
371 | return "Could not find the '/:route' route. Try checking spelling is correct and the '/:route' is registered correctly in your routes files. You can also run 'python craft routes:list' to make sure the route shows up correctly"
372 |
373 | def regex(self):
374 | return r"(?P([\w]*)) \: 404 Not Found"
375 |
376 |
377 | class ModelNotFound:
378 | def title(self):
379 | return "No record found when using find_or_fail()"
380 |
381 | def description(self):
382 | return """You probably used 'find_or_fail()' method on a Model, and no record has been found with the given primary key, a ModelNotFound exception has then been raised with a 404 error code.
383 | If you want this error to be silent you can use 'find()' method instead."""
384 |
385 | def regex(self):
386 | return r"No record found with the given primary key"
387 |
388 |
389 | class DriverNotFound:
390 | def title(self):
391 | return "Driver Is Not Installed"
392 |
393 | def description(self):
394 | return ":package is required by the driver. You should install it with 'pip install :package' and refresh the page."
395 |
396 | def regex(self):
397 | return r"^Could not find the '(?P([\w]*))' library"
398 |
399 |
400 | class MethodNotAllowed:
401 | def title(self):
402 | return "HTTP Method Not Allowed"
403 |
404 | def description(self):
405 | return "You tried to make a :method request on this URL but only :allowed_methods methods are allowed. If you want to use this method, update your routes file else use the allowed methods for making the request to this URL."
406 |
407 | def regex(self):
408 | return r"^(?P([\w]*)) method not allowed for this route. Supported methods are: (?P(\w+\,?\s?)*)."
409 | # return r"^(?P([\w+])) method not allowed for this route. Supported methods are: (?P(\w,+))."
410 |
411 |
412 | class DatabaseDriverNotFound:
413 | def title(self):
414 | return "Database Driver Is Not Installed"
415 |
416 | def description(self):
417 | return ":package is required by the database driver. You should install it with 'pip install :package' and refresh the page."
418 |
419 | def regex(self):
420 | return r"^You must have the '(?P([\w]*))' package installed"
421 |
422 |
423 | class InvalidRouteMethodType:
424 | def title(self):
425 | return "The method type is incorrect"
426 |
427 | def description(self):
428 | return "If this is a GET route, check if the route is actually defined as Route.post(). Or the opposite"
429 |
430 | def regex(self):
431 | return r"(?P([\w]*)) \: 404 Not Found"
432 |
433 |
434 | class GetAttributeObject:
435 | def title(self):
436 | return "Check the class method exists"
437 |
438 | def description(self):
439 | return """
440 | Double check the object you are using and make sure it has the ':attribute' attribute.
441 |
442 | If you are using a builtin python type then check Python documentation.
443 | If you are using your own class then check the available methods.
444 | """
445 |
446 | def regex(self):
447 | return r"^'(?P