Pyjoyment 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
Pyjo/Exception.py
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Pyjo.Exception - Exception base class
|
|
3
|
+
=====================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
import Pyjo.Exception
|
|
7
|
+
|
|
8
|
+
# Create exception classes
|
|
9
|
+
class MyApp_X_Foo(Pyjo.Exception.object):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
class MyApp_X_Bar(Pyjo.Exception.object):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
# Throw exceptions and handle them gracefully
|
|
16
|
+
from Pyjo.Exception import check
|
|
17
|
+
try:
|
|
18
|
+
MyApp_X_Foo.throw('Something went wrong!')
|
|
19
|
+
except Exception as e:
|
|
20
|
+
check(e, [
|
|
21
|
+
(MyApp_X_Foo, lambda e: print(f'Foo: {e}')),
|
|
22
|
+
(MyApp_X_Bar, lambda e: print(f'Bar: {e}')),
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
# Generate exception classes on demand
|
|
26
|
+
from Pyjo.Exception import check, throw
|
|
27
|
+
try:
|
|
28
|
+
throw('MyApp.X.Name', 'The name Minion is already taken')
|
|
29
|
+
except Exception as e:
|
|
30
|
+
check(e, [
|
|
31
|
+
('MyApp.X.Name', lambda e: print(f'Name error: {e}')),
|
|
32
|
+
('default', lambda e: print(f'Error: {e}')),
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
:mod:`Pyjo.Exception` is a container for exceptions with context
|
|
36
|
+
information. It is an :class:`Exception`, so it is raised and caught as the
|
|
37
|
+
exceptions of Python are.
|
|
38
|
+
|
|
39
|
+
See also
|
|
40
|
+
--------
|
|
41
|
+
|
|
42
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
43
|
+
|
|
44
|
+
Ported from `Mojo::Exception <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Exception.pm>`_
|
|
45
|
+
of Mojolicious 9.49.
|
|
46
|
+
|
|
47
|
+
Functions
|
|
48
|
+
---------
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
import Pyjo.Base
|
|
52
|
+
import Pyjo.String.Mixin
|
|
53
|
+
|
|
54
|
+
from Pyjo.Util import decode
|
|
55
|
+
|
|
56
|
+
import builtins
|
|
57
|
+
import importlib
|
|
58
|
+
import os
|
|
59
|
+
import re
|
|
60
|
+
import sys
|
|
61
|
+
import traceback
|
|
62
|
+
|
|
63
|
+
from collections.abc import Callable
|
|
64
|
+
from typing import Any, NoReturn, Self, cast
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
type Anything = builtins.object
|
|
68
|
+
|
|
69
|
+
# A pattern of check(): "default", a class, its name or a regular expression
|
|
70
|
+
type Pattern = str | type[BaseException] | re.Pattern[str]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Pyjo_Exception(Exception, Pyjo.Base.object, Pyjo.String.Mixin.object):
|
|
74
|
+
"""
|
|
75
|
+
:mod:`Pyjo.Exception` inherits all methods from :class:`Exception`,
|
|
76
|
+
:mod:`Pyjo.Base` and :mod:`Pyjo.String.Mixin` and implements the following
|
|
77
|
+
new ones.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, message: Anything = None) -> None:
|
|
81
|
+
"""::
|
|
82
|
+
|
|
83
|
+
e = Pyjo.Exception.new()
|
|
84
|
+
e = Pyjo.Exception.new('Died at test.py line 3.')
|
|
85
|
+
e = Pyjo.Exception.new(ValueError('Something went wrong!'))
|
|
86
|
+
|
|
87
|
+
Construct a new :mod:`Pyjo.Exception` object and assign
|
|
88
|
+
:attr:`message` if necessary. An exception of Python gives its message
|
|
89
|
+
and the :attr:`frames` of its traceback.
|
|
90
|
+
"""
|
|
91
|
+
self.frames: list[traceback.FrameSummary] = []
|
|
92
|
+
"""::
|
|
93
|
+
|
|
94
|
+
frames = e.frames
|
|
95
|
+
e.frames = [frame1, frame2]
|
|
96
|
+
|
|
97
|
+
Stack trace if available, as a list of
|
|
98
|
+
:class:`traceback.FrameSummary`, the most recent first. ::
|
|
99
|
+
|
|
100
|
+
# Extract information from the last frame
|
|
101
|
+
filename, lineno, name, line = e.frames[-1]
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
self.line: list[int | str] = []
|
|
105
|
+
"""::
|
|
106
|
+
|
|
107
|
+
line = e.line
|
|
108
|
+
e.line = [3, 'raise Exception()']
|
|
109
|
+
|
|
110
|
+
The line where the exception occurred if available.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
self.lines_after: list[list[int | str]] = []
|
|
114
|
+
"""::
|
|
115
|
+
|
|
116
|
+
lines = e.lines_after
|
|
117
|
+
e.lines_after = [[4, 'print(foo)'], [5, 'print(bar)']]
|
|
118
|
+
|
|
119
|
+
Lines after the line where the exception occurred if available.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
self.lines_before: list[list[int | str]] = []
|
|
123
|
+
"""::
|
|
124
|
+
|
|
125
|
+
lines = e.lines_before
|
|
126
|
+
e.lines_before = [[1, 'foo = 23'], [2, 'bar = 24']]
|
|
127
|
+
|
|
128
|
+
Lines before the line where the exception occurred if available.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
self.message: str = 'Exception!'
|
|
132
|
+
"""::
|
|
133
|
+
|
|
134
|
+
msg = e.message
|
|
135
|
+
e.message = 'Died at test.py line 3.'
|
|
136
|
+
|
|
137
|
+
Exception message, defaults to ``Exception!``.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
self._verbose: bool | None = None
|
|
141
|
+
|
|
142
|
+
# Not in Mojolicious: an exception of Python
|
|
143
|
+
if isinstance(message, BaseException):
|
|
144
|
+
self.message = str(message)
|
|
145
|
+
if message.__traceback__ is not None:
|
|
146
|
+
self.frames = list(reversed(traceback.extract_tb(message.__traceback__)))
|
|
147
|
+
elif message is not None:
|
|
148
|
+
self.message = str(message)
|
|
149
|
+
|
|
150
|
+
super().__init__(self.message)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def verbose(self) -> bool:
|
|
154
|
+
"""::
|
|
155
|
+
|
|
156
|
+
boolean = e.verbose
|
|
157
|
+
e.verbose = boolean
|
|
158
|
+
|
|
159
|
+
Show more information with :meth:`to_str`, such as :attr:`frames`,
|
|
160
|
+
defaults to the value of the ``PYJO_EXCEPTION_VERBOSE`` environment
|
|
161
|
+
variable.
|
|
162
|
+
"""
|
|
163
|
+
if self._verbose is None:
|
|
164
|
+
self._verbose = bool(os.environ.get('PYJO_EXCEPTION_VERBOSE'))
|
|
165
|
+
return self._verbose
|
|
166
|
+
|
|
167
|
+
@verbose.setter
|
|
168
|
+
def verbose(self, value: bool) -> None:
|
|
169
|
+
self._verbose = value
|
|
170
|
+
|
|
171
|
+
def __bool__(self) -> bool:
|
|
172
|
+
"""::
|
|
173
|
+
|
|
174
|
+
boolean = bool(e)
|
|
175
|
+
|
|
176
|
+
Always true.
|
|
177
|
+
"""
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def __str__(self) -> str:
|
|
181
|
+
"""::
|
|
182
|
+
|
|
183
|
+
string = str(e)
|
|
184
|
+
|
|
185
|
+
Alias for :meth:`to_str`.
|
|
186
|
+
"""
|
|
187
|
+
return self.to_str()
|
|
188
|
+
|
|
189
|
+
def inspect(self, *sources: str) -> Self:
|
|
190
|
+
"""::
|
|
191
|
+
|
|
192
|
+
e = e.inspect()
|
|
193
|
+
e = e.inspect(source1, source2)
|
|
194
|
+
|
|
195
|
+
Inspect :attr:`message`, :attr:`frames` and optional additional sources
|
|
196
|
+
to fill :attr:`lines_before`, :attr:`line` and :attr:`lines_after` with
|
|
197
|
+
context information.
|
|
198
|
+
"""
|
|
199
|
+
if len(self.line):
|
|
200
|
+
return self
|
|
201
|
+
|
|
202
|
+
# Extract file and line from message
|
|
203
|
+
files = [(m[1], int(m[2])) for m in re.finditer(r'at\s+(.+?)\s+line\s+(\d+)', self.message)][::-1]
|
|
204
|
+
|
|
205
|
+
# Extract file and line from stack trace
|
|
206
|
+
if len(self.frames) and (zero := self.frames[0]).lineno is not None:
|
|
207
|
+
files.append((zero.filename, zero.lineno))
|
|
208
|
+
|
|
209
|
+
# Search for context in files
|
|
210
|
+
for file, num in files:
|
|
211
|
+
try:
|
|
212
|
+
with open(file, 'rb') as handle:
|
|
213
|
+
data = handle.read()
|
|
214
|
+
except OSError:
|
|
215
|
+
continue
|
|
216
|
+
self._context(num, [re.findall(rb'[^\n]*\n|[^\n]+$', data)])
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
# Search for context in sources
|
|
220
|
+
if len(sources) and len(files):
|
|
221
|
+
self._context(files[-1][1], [_split(s) for s in sources])
|
|
222
|
+
|
|
223
|
+
return self
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def throw(cls, message: Anything = None) -> NoReturn:
|
|
227
|
+
"""::
|
|
228
|
+
|
|
229
|
+
Pyjo.Exception.object.throw('Something went wrong!')
|
|
230
|
+
|
|
231
|
+
Throw exception from the current execution context. ::
|
|
232
|
+
|
|
233
|
+
# Longer version
|
|
234
|
+
raise Pyjo.Exception.new('Something went wrong!').trace()
|
|
235
|
+
"""
|
|
236
|
+
raise cls.new(message).trace(2)
|
|
237
|
+
|
|
238
|
+
# Renamed from Mojolicious: to_string
|
|
239
|
+
def to_str(self) -> str:
|
|
240
|
+
"""::
|
|
241
|
+
|
|
242
|
+
string = e.to_str()
|
|
243
|
+
|
|
244
|
+
Render exception. Note that the output format may change as more
|
|
245
|
+
features are added, only the error message at the beginning is
|
|
246
|
+
guaranteed not to be modified to allow regex matching.
|
|
247
|
+
"""
|
|
248
|
+
string = self.message
|
|
249
|
+
|
|
250
|
+
frames = self.frames
|
|
251
|
+
if not string.endswith('\n'):
|
|
252
|
+
string += f' at {frames[0].filename} line {frames[0].lineno}.\n' if len(frames) else '\n'
|
|
253
|
+
if not self.verbose:
|
|
254
|
+
return string
|
|
255
|
+
|
|
256
|
+
line = self.line
|
|
257
|
+
if len(line):
|
|
258
|
+
string += 'Context:\n'
|
|
259
|
+
string += ''.join(f' {n[0]}: {n[1]}\n' for n in self.lines_before)
|
|
260
|
+
string += f' {line[0]}: {line[1]}\n'
|
|
261
|
+
string += ''.join(f' {n[0]}: {n[1]}\n' for n in self.lines_after)
|
|
262
|
+
|
|
263
|
+
if len(frames):
|
|
264
|
+
string += 'Traceback (most recent call first):\n'
|
|
265
|
+
string += ''.join(f' File "{f.filename}", line {f.lineno}, in "{f.name}"\n' for f in frames)
|
|
266
|
+
|
|
267
|
+
return string
|
|
268
|
+
|
|
269
|
+
def trace(self, start: int = 1) -> Self:
|
|
270
|
+
"""::
|
|
271
|
+
|
|
272
|
+
e = e.trace()
|
|
273
|
+
e = e.trace(skip)
|
|
274
|
+
|
|
275
|
+
Generate stack trace and store all :attr:`frames`, defaults to skipping
|
|
276
|
+
``1`` call frame. ::
|
|
277
|
+
|
|
278
|
+
# Skip 3 call frames
|
|
279
|
+
e.trace(3)
|
|
280
|
+
|
|
281
|
+
# Skip no call frames
|
|
282
|
+
e.trace(0)
|
|
283
|
+
"""
|
|
284
|
+
try:
|
|
285
|
+
frame = sys._getframe(start) # pyright: ignore[reportPrivateUsage]
|
|
286
|
+
except ValueError:
|
|
287
|
+
self.frames = []
|
|
288
|
+
else:
|
|
289
|
+
self.frames = list(reversed(traceback.extract_stack(frame)))
|
|
290
|
+
return self
|
|
291
|
+
|
|
292
|
+
# Left out from Mojolicious: with_roles, as Pyjoyment has no roles
|
|
293
|
+
|
|
294
|
+
def _context(self, num: int, sources: list[list[bytes]] | list[list[str]]) -> None:
|
|
295
|
+
|
|
296
|
+
# Line
|
|
297
|
+
if not 0 <= num - 1 < len(sources[0]):
|
|
298
|
+
return
|
|
299
|
+
self.line = [num]
|
|
300
|
+
for s in sources:
|
|
301
|
+
self.line.append(_line(s, num - 1))
|
|
302
|
+
|
|
303
|
+
# Before
|
|
304
|
+
for i in range(2, 7):
|
|
305
|
+
if (previous := num - i) < 0:
|
|
306
|
+
break
|
|
307
|
+
self.lines_before.insert(0, [previous + 1, *(_line(s, previous) for s in sources)])
|
|
308
|
+
|
|
309
|
+
# After
|
|
310
|
+
for i in range(5):
|
|
311
|
+
if (following := num + i) < 0 or following >= len(sources[0]):
|
|
312
|
+
continue
|
|
313
|
+
self.lines_after.append([following + 1, *(_line(s, following) for s in sources)])
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def check(
|
|
317
|
+
err: Anything, spec: list[tuple[Pattern | list[Pattern] | tuple[Pattern, ...], Callable[[Any], Anything]]]
|
|
318
|
+
) -> bool | None:
|
|
319
|
+
"""::
|
|
320
|
+
|
|
321
|
+
boolean = check(err, [(MyApp_X_Foo, lambda e: ...)])
|
|
322
|
+
|
|
323
|
+
Process exceptions by dispatching them to handlers with one or more
|
|
324
|
+
matching conditions. Exceptions that could not be handled will be raised
|
|
325
|
+
again automatically. ::
|
|
326
|
+
|
|
327
|
+
# Handle various types of exceptions
|
|
328
|
+
try:
|
|
329
|
+
dangerous_code()
|
|
330
|
+
except Exception as e:
|
|
331
|
+
check(e, [
|
|
332
|
+
(MyApp_X_Foo, lambda e: print(f'Foo: {e}')),
|
|
333
|
+
(re.compile(r'^Could not open'), lambda e: print(f'Open error: {e}')),
|
|
334
|
+
('default', lambda e: print(f'Something went wrong: {e}')),
|
|
335
|
+
])
|
|
336
|
+
|
|
337
|
+
Matching conditions can be classes, or their names, for checks with
|
|
338
|
+
:func:`isinstance` on exception objects, or regular expressions to match
|
|
339
|
+
string exceptions and stringified exception objects. The matching
|
|
340
|
+
exception will be the argument passed to the callback. ::
|
|
341
|
+
|
|
342
|
+
# Catch MyApp_X_Foo object or a specific string exception
|
|
343
|
+
try:
|
|
344
|
+
dangerous_code()
|
|
345
|
+
except Exception as e:
|
|
346
|
+
check(e, [
|
|
347
|
+
(MyApp_X_Foo, lambda e: print(f'Foo: {e}')),
|
|
348
|
+
(re.compile(r'^Could not open'), lambda e: print(f'Open error: {e}')),
|
|
349
|
+
])
|
|
350
|
+
|
|
351
|
+
A list can be used to share the same handler with multiple conditions, of
|
|
352
|
+
which only one needs to match. And since exception handlers are just
|
|
353
|
+
callbacks, they can also throw their own exceptions. ::
|
|
354
|
+
|
|
355
|
+
# Handle MyApp_X_Foo and MyApp_X_Bar the same
|
|
356
|
+
try:
|
|
357
|
+
dangerous_code()
|
|
358
|
+
except Exception as e:
|
|
359
|
+
check(e, [
|
|
360
|
+
([MyApp_X_Foo, MyApp_X_Bar], lambda e: Pyjo.Exception.object.throw(f'Foo/Bar: {e}')),
|
|
361
|
+
])
|
|
362
|
+
|
|
363
|
+
There is currently only one keyword you can use to set special handlers.
|
|
364
|
+
The ``default`` handler is used when no other handler matched. ::
|
|
365
|
+
|
|
366
|
+
# Use "default" to catch everything
|
|
367
|
+
try:
|
|
368
|
+
dangerous_code()
|
|
369
|
+
except Exception as e:
|
|
370
|
+
check(e, [
|
|
371
|
+
('default', lambda e: print(f'Error: {e}')),
|
|
372
|
+
])
|
|
373
|
+
"""
|
|
374
|
+
if not err:
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
# The spec is checked as Mojo checks it, for the caller without type hints
|
|
378
|
+
pairs = cast(Anything, spec)
|
|
379
|
+
if not isinstance(pairs, list) or not all(
|
|
380
|
+
isinstance(p, tuple) and len(cast(tuple[Any, ...], p)) == 2 for p in cast(list[Any], pairs)
|
|
381
|
+
):
|
|
382
|
+
# Unlike Mojolicious: Mojo asks for an array reference
|
|
383
|
+
raise TypeError('List of pattern/handler pairs required to dispatch exceptions')
|
|
384
|
+
|
|
385
|
+
default = handler = None
|
|
386
|
+
is_obj, string = not isinstance(err, str), str(err)
|
|
387
|
+
for checks, cb in spec:
|
|
388
|
+
if checks == 'default':
|
|
389
|
+
default = cb
|
|
390
|
+
continue
|
|
391
|
+
|
|
392
|
+
for c in checks if isinstance(checks, (list, tuple)) else [checks]:
|
|
393
|
+
is_re = isinstance(c, re.Pattern)
|
|
394
|
+
if is_obj and not is_re and _isa(err, c):
|
|
395
|
+
handler = cb
|
|
396
|
+
break
|
|
397
|
+
if is_re and c.search(string):
|
|
398
|
+
handler = cb
|
|
399
|
+
break
|
|
400
|
+
if handler is not None:
|
|
401
|
+
break
|
|
402
|
+
|
|
403
|
+
# Rethrow if no handler could be found
|
|
404
|
+
if (handler := handler or default) is None:
|
|
405
|
+
raise err if isinstance(err, BaseException) else Pyjo_Exception(err)
|
|
406
|
+
handler(err)
|
|
407
|
+
|
|
408
|
+
return True
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def throw(first: Anything, *rest: Anything) -> NoReturn:
|
|
412
|
+
"""::
|
|
413
|
+
|
|
414
|
+
throw('Something went wrong!')
|
|
415
|
+
throw('MyApp.X.Foo', 'Something went wrong!')
|
|
416
|
+
throw(MyApp_X_Foo, 'Something went wrong!')
|
|
417
|
+
|
|
418
|
+
Raise a :mod:`Pyjo.Exception`, if the class does not exist yet, one is
|
|
419
|
+
created as a :mod:`Pyjo.Exception` subclass on demand. ``raise_`` is an
|
|
420
|
+
alias, the name of Mojolicious with an underscore.
|
|
421
|
+
"""
|
|
422
|
+
# Renamed from Mojolicious: raise, a keyword of Python
|
|
423
|
+
name, err = (first, rest[0]) if len(rest) else (Pyjo_Exception, first)
|
|
424
|
+
cls = _class(name) if isinstance(name, str) else name
|
|
425
|
+
if not (isinstance(cls, type) and issubclass(cls, Pyjo_Exception)):
|
|
426
|
+
raise TypeError(f'{_name(name)} is not a Pyjo.Exception subclass')
|
|
427
|
+
raise cls.new(err).trace(2)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# The name of Mojolicious, with an underscore as raise is a keyword of Python
|
|
431
|
+
raise_ = throw
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# Mojo makes the class with eval
|
|
435
|
+
def _class(name: str) -> Anything:
|
|
436
|
+
if name in _CLASSES:
|
|
437
|
+
return _CLASSES[name]
|
|
438
|
+
|
|
439
|
+
# The longest prefix that is a module, and the rest its attributes
|
|
440
|
+
parts = name.split('.')
|
|
441
|
+
for i in range(len(parts) - 1, 0, -1):
|
|
442
|
+
try:
|
|
443
|
+
found: Anything = importlib.import_module('.'.join(parts[:i]))
|
|
444
|
+
except ImportError:
|
|
445
|
+
continue
|
|
446
|
+
try:
|
|
447
|
+
for attr in parts[i:]:
|
|
448
|
+
found = getattr(found, attr)
|
|
449
|
+
except AttributeError:
|
|
450
|
+
break
|
|
451
|
+
return found
|
|
452
|
+
|
|
453
|
+
module, _, qualname = name.rpartition('.')
|
|
454
|
+
cls = type(qualname, (Pyjo_Exception,), {'__module__': module or __name__, '__qualname__': qualname})
|
|
455
|
+
_CLASSES[name] = cls
|
|
456
|
+
return cls
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
_CLASSES: dict[str, type[Pyjo_Exception]] = {}
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _isa(err: Anything, cls: Pattern) -> bool:
|
|
463
|
+
if isinstance(cls, type):
|
|
464
|
+
return isinstance(err, cls)
|
|
465
|
+
return any(cls in (c.__qualname__, f'{c.__module__}.{c.__qualname__}') for c in type(err).__mro__)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
# A line of a source, which is empty where an additional source is shorter, as Perl reads undef there
|
|
469
|
+
def _line(source: list[bytes] | list[str], i: int) -> str:
|
|
470
|
+
if i >= len(source):
|
|
471
|
+
return ''
|
|
472
|
+
line = source[i]
|
|
473
|
+
if isinstance(line, bytes):
|
|
474
|
+
decoded = decode('UTF-8', line)
|
|
475
|
+
line = decoded if decoded is not None else line.decode('latin-1')
|
|
476
|
+
return line[:-1] if line.endswith('\n') else line
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _name(cls: Anything) -> str:
|
|
480
|
+
if isinstance(cls, type):
|
|
481
|
+
return f'{cls.__module__}.{cls.__qualname__}'
|
|
482
|
+
return str(cls)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# Perl's split /\n/, which drops the empty lines at the end
|
|
486
|
+
def _split(source: str) -> list[str]:
|
|
487
|
+
lines = source.split('\n')
|
|
488
|
+
while len(lines) and lines[-1] == '':
|
|
489
|
+
lines.pop()
|
|
490
|
+
return lines
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
new = Pyjo_Exception.new
|
|
494
|
+
object = Pyjo_Exception
|