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/Loader.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Pyjo.Loader - Load all kinds of things
|
|
3
|
+
======================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Loader import data_section, find_modules, load_class
|
|
7
|
+
from Pyjo.Util import warn
|
|
8
|
+
|
|
9
|
+
# Find modules in a namespace
|
|
10
|
+
for module in find_modules('Some.Namespace'):
|
|
11
|
+
|
|
12
|
+
# Load them safely
|
|
13
|
+
e = load_class(module)
|
|
14
|
+
if isinstance(e, Exception):
|
|
15
|
+
warn(f'Loading "{module}" failed: {e}')
|
|
16
|
+
continue
|
|
17
|
+
|
|
18
|
+
# And extract files from the DATA section
|
|
19
|
+
print(data_section(module, 'some_file.txt'))
|
|
20
|
+
|
|
21
|
+
:mod:`Pyjo.Loader` is a class loader and plugin framework. Aside from finding
|
|
22
|
+
modules and loading classes, it allows multiple files to be stored in the
|
|
23
|
+
``DATA`` section of a module, which can then be accessed individually. The
|
|
24
|
+
section is the ``__DATA__`` variable of the module, or its docstring after a
|
|
25
|
+
line ``__DATA__``. ::
|
|
26
|
+
|
|
27
|
+
# Foo.py
|
|
28
|
+
|
|
29
|
+
__DATA__ = r'''
|
|
30
|
+
@@ test.txt
|
|
31
|
+
This is the first file.
|
|
32
|
+
|
|
33
|
+
@@ test2.html (base64)
|
|
34
|
+
VGhpcyBpcyB0aGUgc2Vjb25kIGZpbGUu
|
|
35
|
+
|
|
36
|
+
@@ test
|
|
37
|
+
This is the
|
|
38
|
+
third file.
|
|
39
|
+
'''
|
|
40
|
+
|
|
41
|
+
Each file has a header starting with ``@@``, followed by the file name and
|
|
42
|
+
optional instructions for decoding its content. Currently only the Base64
|
|
43
|
+
encoding is supported, which can be quite convenient for the storage of binary
|
|
44
|
+
data.
|
|
45
|
+
|
|
46
|
+
A class is named as a module of Python, with dots, and is loaded from
|
|
47
|
+
:data:`sys.path`. A function that takes a class takes the module, a class, or
|
|
48
|
+
the dotted name of a module or of a class in it.
|
|
49
|
+
|
|
50
|
+
See also
|
|
51
|
+
--------
|
|
52
|
+
|
|
53
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
54
|
+
|
|
55
|
+
Ported from `Mojo::Loader <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Loader.pm>`_
|
|
56
|
+
of Mojolicious 9.49.
|
|
57
|
+
|
|
58
|
+
Functions
|
|
59
|
+
---------
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
import Pyjo.Exception
|
|
63
|
+
import Pyjo.File
|
|
64
|
+
import Pyjo.String.Unicode
|
|
65
|
+
|
|
66
|
+
from Pyjo.Util import b64_decode
|
|
67
|
+
|
|
68
|
+
import importlib
|
|
69
|
+
import importlib.util
|
|
70
|
+
import os
|
|
71
|
+
import re
|
|
72
|
+
import sys
|
|
73
|
+
import traceback
|
|
74
|
+
|
|
75
|
+
from importlib.machinery import ModuleSpec
|
|
76
|
+
from types import ModuleType
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# A class: its module, a class in it, or the dotted name of either
|
|
80
|
+
type Class = ModuleType | type | str
|
|
81
|
+
|
|
82
|
+
_BIN: dict[str, dict[str, bool]] = {}
|
|
83
|
+
_CACHE: dict[str, dict[str, str | bytes]] = {}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def data_section(cls: Class | None, name: str | None = None) -> 'dict[str, str | bytes] | str | bytes | None':
|
|
87
|
+
"""::
|
|
88
|
+
|
|
89
|
+
all = data_section('Foo.Bar')
|
|
90
|
+
index = data_section('Foo.Bar', 'index.html')
|
|
91
|
+
|
|
92
|
+
Extract embedded file from the ``DATA`` section of a module, all files
|
|
93
|
+
will be cached once they have been accessed for the first time. A file
|
|
94
|
+
encoded in Base64 is :class:`bytes`, any other a :class:`str`. ::
|
|
95
|
+
|
|
96
|
+
# List embedded files
|
|
97
|
+
for name in data_section('Foo.Bar'):
|
|
98
|
+
print(name)
|
|
99
|
+
"""
|
|
100
|
+
if not cls:
|
|
101
|
+
return None
|
|
102
|
+
return _all(cls).get(name) if name else _all(cls)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def file_is_binary(cls: Class, name: str) -> bool | None:
|
|
106
|
+
"""::
|
|
107
|
+
|
|
108
|
+
boolean = file_is_binary('Foo.Bar', 'test.png')
|
|
109
|
+
|
|
110
|
+
Check if embedded file from the ``DATA`` section of a module was Base64
|
|
111
|
+
encoded.
|
|
112
|
+
"""
|
|
113
|
+
return bool(_BIN.get(_key(cls) or '', {}).get(name)) if len(_all(cls)) else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def find_modules(ns: str, *, recursive: bool = False) -> list[str]:
|
|
117
|
+
"""::
|
|
118
|
+
|
|
119
|
+
modules = find_modules('MyApp.Namespace')
|
|
120
|
+
modules = find_modules('MyApp.Namespace', recursive=True)
|
|
121
|
+
|
|
122
|
+
Search for modules in a namespace, in the directories of
|
|
123
|
+
:data:`sys.path`, without loading them. A package, a directory with an
|
|
124
|
+
``__init__.py``, is a module too.
|
|
125
|
+
|
|
126
|
+
These options are currently available:
|
|
127
|
+
|
|
128
|
+
recursive
|
|
129
|
+
::
|
|
130
|
+
|
|
131
|
+
recursive=True
|
|
132
|
+
|
|
133
|
+
Search namespace recursively.
|
|
134
|
+
"""
|
|
135
|
+
parts = ns.split('.')
|
|
136
|
+
dirs = [d for d in (Pyjo.File.new(p or '.', *parts) for p in sys.path) if os.path.isdir(d)]
|
|
137
|
+
|
|
138
|
+
modules: set[str] = set()
|
|
139
|
+
for dir in dirs:
|
|
140
|
+
for file in dir.list_tree() if recursive else dir.list(dir=True):
|
|
141
|
+
path = file.to_str()
|
|
142
|
+
|
|
143
|
+
# Not in Mojolicious: a package is a module, as Foo.pm next to Foo/ is in Perl
|
|
144
|
+
if os.path.isdir(path):
|
|
145
|
+
if not os.path.isfile(path := os.path.join(path, '__init__.py')):
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
if not path.endswith('.py'):
|
|
149
|
+
continue
|
|
150
|
+
rel = Pyjo.File.new(path[:-3]).to_rel(dir).to_list()
|
|
151
|
+
if rel[-1] == '__init__':
|
|
152
|
+
rel.pop()
|
|
153
|
+
if len(rel):
|
|
154
|
+
modules.add('.'.join([ns, *rel]))
|
|
155
|
+
|
|
156
|
+
return sorted(modules)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def find_packages(ns: str) -> list[str]:
|
|
160
|
+
"""::
|
|
161
|
+
|
|
162
|
+
pkgs = find_packages('MyApp.Namespace')
|
|
163
|
+
|
|
164
|
+
Search for packages in a namespace non-recursively: the modules in it that
|
|
165
|
+
are loaded, as :data:`sys.modules` has them.
|
|
166
|
+
"""
|
|
167
|
+
prefix = f'{ns}.'
|
|
168
|
+
return sorted(n for n in list(sys.modules) if n.startswith(prefix) and '.' not in n[len(prefix) :])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def load_class(cls: str) -> 'Pyjo.Exception.object | bool | None':
|
|
172
|
+
"""::
|
|
173
|
+
|
|
174
|
+
e = load_class('Foo.Bar')
|
|
175
|
+
|
|
176
|
+
Load a class and catch exceptions, returns a false value if loading was
|
|
177
|
+
successful, a true value if the class was not found, or a
|
|
178
|
+
:mod:`Pyjo.Exception` object if loading failed. A directory without an
|
|
179
|
+
``__init__.py`` (a namespace package) is not a class. The context of the
|
|
180
|
+
exception is the line of the module that failed to load, and the exception
|
|
181
|
+
of Python is its ``__cause__``. ::
|
|
182
|
+
|
|
183
|
+
# Handle exceptions
|
|
184
|
+
if e := load_class('Foo.Bar'):
|
|
185
|
+
raise Exception(f'Exception: {e}' if isinstance(e, Exception) else 'Not found!')
|
|
186
|
+
"""
|
|
187
|
+
# Invalid class name
|
|
188
|
+
if not re.fullmatch(r'\w(?:[\w.]*\w)?', cls or ''):
|
|
189
|
+
return True
|
|
190
|
+
# Left out from Mojolicious: the deprecated package separator "'", which Python has never had
|
|
191
|
+
|
|
192
|
+
# Load if not already loaded
|
|
193
|
+
module = sys.modules.get(cls)
|
|
194
|
+
try:
|
|
195
|
+
spec = module.__spec__ if module is not None else _find_spec(cls)
|
|
196
|
+
except ModuleNotFoundError as e:
|
|
197
|
+
# Does not exist
|
|
198
|
+
if e.name is not None and (cls == e.name or cls.startswith(f'{e.name}.')):
|
|
199
|
+
return True
|
|
200
|
+
return _exception(e, None)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
return _exception(e, None)
|
|
203
|
+
if module is not None and spec is None:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
# Does not exist, or a namespace package
|
|
207
|
+
if spec is None or spec.origin is None:
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
importlib.import_module(cls)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
# Real error
|
|
214
|
+
return _exception(e, spec.origin)
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def load_classes(ns: str) -> list[str]:
|
|
219
|
+
"""::
|
|
220
|
+
|
|
221
|
+
classes = load_classes('Foo.Bar')
|
|
222
|
+
|
|
223
|
+
Load all classes in a namespace recursively.
|
|
224
|
+
"""
|
|
225
|
+
classes: list[str] = []
|
|
226
|
+
for module in find_modules(ns, recursive=True):
|
|
227
|
+
if not (e := load_class(module)):
|
|
228
|
+
classes.append(module)
|
|
229
|
+
if isinstance(e, Exception):
|
|
230
|
+
raise e
|
|
231
|
+
|
|
232
|
+
return classes
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _all(cls: Class) -> dict[str, str | bytes]:
|
|
236
|
+
if (module := _module(cls)) is None:
|
|
237
|
+
return {}
|
|
238
|
+
name = module.__name__
|
|
239
|
+
if name in _CACHE:
|
|
240
|
+
return _CACHE[name]
|
|
241
|
+
|
|
242
|
+
# Not in Mojolicious: the __DATA__ variable of a module, or its docstring after a line __DATA__
|
|
243
|
+
data = getattr(module, '__DATA__', None)
|
|
244
|
+
if not isinstance(data, str):
|
|
245
|
+
doc = module.__doc__
|
|
246
|
+
if not isinstance(doc, str) or not re.search(r'^__DATA__\r?$', doc, re.M):
|
|
247
|
+
return {}
|
|
248
|
+
data = doc
|
|
249
|
+
|
|
250
|
+
# Ignore everything before __DATA__
|
|
251
|
+
data = re.sub(r'\A.*\n__DATA__\r?\n', '\n', data, count=1, flags=re.S)
|
|
252
|
+
|
|
253
|
+
# Ignore everything after __END__
|
|
254
|
+
data = re.sub(r'\n__END__\r?\n.*\Z', '\n', data, count=1, flags=re.S)
|
|
255
|
+
|
|
256
|
+
# Split files
|
|
257
|
+
files = Pyjo.String.Unicode.split(re.compile(r'^@@\s*(.+?)\s*\r?\n', re.M), data)[1:]
|
|
258
|
+
|
|
259
|
+
# Find data
|
|
260
|
+
all: dict[str, str | bytes] = {}
|
|
261
|
+
_CACHE[name] = all
|
|
262
|
+
while len(files):
|
|
263
|
+
file, content = files[0] or '', files[1] if len(files) > 1 else None
|
|
264
|
+
del files[:2]
|
|
265
|
+
content = content or ''
|
|
266
|
+
if (base := re.sub(r'\s*\(\s*base64\s*\)$', '', file)) != file:
|
|
267
|
+
_BIN.setdefault(name, {})[base] = True
|
|
268
|
+
all[base] = b64_decode(content)
|
|
269
|
+
else:
|
|
270
|
+
all[file] = content
|
|
271
|
+
|
|
272
|
+
return all
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# The exception of a module that failed to load, with the context of the line of the module
|
|
276
|
+
def _exception(e: Exception, origin: str | None) -> Pyjo.Exception.object:
|
|
277
|
+
err = Pyjo.Exception.new(e)
|
|
278
|
+
err.__cause__ = e
|
|
279
|
+
|
|
280
|
+
# Unlike Mojolicious: croak reports the line of the caller, Python the line of the raise, so the frames of the
|
|
281
|
+
# module come first, or the location of a syntax error, which is not in a frame
|
|
282
|
+
if isinstance(e, SyntaxError) and e.filename is not None and e.lineno is not None:
|
|
283
|
+
err.frames.insert(0, traceback.FrameSummary(e.filename, e.lineno, '<module>', lookup_line=False, line=e.text))
|
|
284
|
+
elif origin is not None:
|
|
285
|
+
for i, frame in enumerate(err.frames):
|
|
286
|
+
if frame.filename == origin and frame.name == '<module>':
|
|
287
|
+
del err.frames[:i]
|
|
288
|
+
break
|
|
289
|
+
|
|
290
|
+
return err.inspect()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# The module of a class, or the longest prefix of a dotted name that is loaded
|
|
294
|
+
# Not in Mojolicious: the finders of importlib keep the files of a directory until its time of modification changes,
|
|
295
|
+
# which may not show a module written right after they looked, where require of Perl looks at the files each time, so a
|
|
296
|
+
# module that is not found is looked for again without them
|
|
297
|
+
def _find_spec(cls: str) -> ModuleSpec | None:
|
|
298
|
+
try:
|
|
299
|
+
if (spec := importlib.util.find_spec(cls)) is not None:
|
|
300
|
+
return spec
|
|
301
|
+
except ModuleNotFoundError:
|
|
302
|
+
pass
|
|
303
|
+
importlib.invalidate_caches()
|
|
304
|
+
return importlib.util.find_spec(cls)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _module(cls: Class) -> ModuleType | None:
|
|
308
|
+
if isinstance(cls, ModuleType):
|
|
309
|
+
return cls
|
|
310
|
+
name = cls.__module__ if isinstance(cls, type) else cls
|
|
311
|
+
parts = name.split('.')
|
|
312
|
+
for i in range(len(parts), 0, -1):
|
|
313
|
+
if (module := sys.modules.get('.'.join(parts[:i]))) is not None:
|
|
314
|
+
return module
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# The name of the module of a class, the key of the cache
|
|
319
|
+
def _key(cls: Class) -> str | None:
|
|
320
|
+
return module.__name__ if (module := _module(cls)) is not None else None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# Not in Mojolicious: the class of a dotted name, which a Perl package is, for the modules of Pyjoyment that load
|
|
324
|
+
# plugins and controllers by name: the class that a module names its object, or an attribute of the longest prefix of
|
|
325
|
+
# the name that is a module
|
|
326
|
+
def _class(name: str) -> type | None: # pyright: ignore[reportUnusedFunction]
|
|
327
|
+
if (module := sys.modules.get(name)) is not None:
|
|
328
|
+
found: object = getattr(module, 'object', None)
|
|
329
|
+
return found if isinstance(found, type) else None
|
|
330
|
+
parts = name.split('.')
|
|
331
|
+
for i in range(len(parts) - 1, 0, -1):
|
|
332
|
+
try:
|
|
333
|
+
found = importlib.import_module('.'.join(parts[:i]))
|
|
334
|
+
except ImportError:
|
|
335
|
+
continue
|
|
336
|
+
try:
|
|
337
|
+
for attr in parts[i:]:
|
|
338
|
+
found = getattr(found, attr)
|
|
339
|
+
except AttributeError:
|
|
340
|
+
return None
|
|
341
|
+
return found if isinstance(found, type) else None
|
|
342
|
+
return None
|