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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment.Plugin.Mount - Application mount plugin
|
|
3
|
+
=================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
# Pyjoyment
|
|
7
|
+
route = app.plugin('Mount', {'/prefix': '/home/sri/foo/script/foo'})
|
|
8
|
+
|
|
9
|
+
# Pyjoyment.Lite
|
|
10
|
+
route = plugin('Mount', {'/prefix': '/home/sri/myapp.py'})
|
|
11
|
+
|
|
12
|
+
# Adjust the generated route and mounted application
|
|
13
|
+
example = plugin('Mount', {'/example': '/home/sri/example.py'})
|
|
14
|
+
example.to(message='It works great!')
|
|
15
|
+
embedded = example.pattern.defaults['app']
|
|
16
|
+
embedded.config(foo='bar')
|
|
17
|
+
embedded.log = app.log
|
|
18
|
+
|
|
19
|
+
# Mount application with host
|
|
20
|
+
plugin('Mount', {'example.com': '/home/sri/myapp.py'})
|
|
21
|
+
|
|
22
|
+
# Host and path
|
|
23
|
+
plugin('Mount', {'example.com/myapp': '/home/sri/myapp.py'})
|
|
24
|
+
|
|
25
|
+
# Or even hosts with wildcard subdomains
|
|
26
|
+
plugin('Mount', {'*.example.com/myapp': '/home/sri/myapp.py'})
|
|
27
|
+
|
|
28
|
+
# Application object
|
|
29
|
+
plugin('Mount', {'/prefix': MyApp.new()})
|
|
30
|
+
|
|
31
|
+
# WSGI application, as an object or as the name of its module and its
|
|
32
|
+
# attribute
|
|
33
|
+
plugin('Mount', {'/flask': flask_app})
|
|
34
|
+
plugin('Mount', {'/flask': 'myflask:app'})
|
|
35
|
+
|
|
36
|
+
# ASGI application, as an object or as the name of its module and its
|
|
37
|
+
# attribute
|
|
38
|
+
plugin('Mount', {'/starlette': starlette_app})
|
|
39
|
+
plugin('Mount', {'/starlette': 'mystarlette:app'})
|
|
40
|
+
|
|
41
|
+
# FastCGI application, as PHP on php-fpm, by its location, with the
|
|
42
|
+
# directory of its scripts and the one entry of every other path
|
|
43
|
+
plugin('Mount', {'/php': 'fcgi://127.0.0.1:9000'})
|
|
44
|
+
plugin('Mount', {'/php': 'fcgi+unix://%2Frun%2Fphp%2Ffpm.sock'})
|
|
45
|
+
plugin('Mount', {'/blog': 'fcgi://127.0.0.1:9000?document_root=%2Fvar%2Fwww&script=index.php'})
|
|
46
|
+
|
|
47
|
+
# CGI application, as an object, as a string is the path of a script
|
|
48
|
+
plugin('Mount', {'/cgi-bin': Pyjo.App.CGI.new(document_root='/usr/lib/cgi-bin')})
|
|
49
|
+
|
|
50
|
+
# Event function of AWS Lambda, as an object, in a process of its own
|
|
51
|
+
plugin('Mount', {'/function': Pyjo.App.Lambda.new(function='myfunction.handler')})
|
|
52
|
+
|
|
53
|
+
# A regular expression of the whole path, whose group path is the rest of
|
|
54
|
+
# the path for the application, and whose other named groups are captures
|
|
55
|
+
plugin('Mount', {re.compile(r'/php(?P<path>/.*)?'): flask_app})
|
|
56
|
+
plugin('Mount', {re.compile(r'/(?P<user>\\w+)/blog(?P<path>/.*)?'): flask_app})
|
|
57
|
+
|
|
58
|
+
:mod:`Pyjoyment.Plugin.Mount` is a plugin that allows you to mount whole
|
|
59
|
+
:mod:`Pyjoyment` applications, other applications of :mod:`Pyjo`, ``WSGI``
|
|
60
|
+
applications, which :mod:`Pyjo.App.WSGI` runs, and ``ASGI`` applications,
|
|
61
|
+
whose ``__call__`` is a coroutine function, which :mod:`Pyjo.App.ASGI` runs,
|
|
62
|
+
as an application of :mod:`Pyjo` from a script, an object, or the name of a
|
|
63
|
+
module and its attribute, and ``FastCGI`` applications, which
|
|
64
|
+
:mod:`Pyjo.App.FastCGI` connects to, by their location ``fcgi://`` or
|
|
65
|
+
``fcgi+unix://``, ``CGI`` programs, which :mod:`Pyjo.App.CGI` runs, and
|
|
66
|
+
event functions of AWS Lambda, which :mod:`Pyjo.App.Lambda` runs, as an
|
|
67
|
+
object of them. An application that is not one of :mod:`Pyjoyment` gets the
|
|
68
|
+
server that runs it with :meth:`~Pyjo.App.Pyjo_App.server` before the server
|
|
69
|
+
starts, as the application that mounts it does.
|
|
70
|
+
|
|
71
|
+
The path is a pattern of a route that matches the beginning of the path of a
|
|
72
|
+
request, as in Mojolicious, so that ``/php`` matches ``/php.css`` too, which
|
|
73
|
+
an application of another framework does not get, as its ``SCRIPT_NAME`` or
|
|
74
|
+
``root_path`` ends at a slash. A regular expression matches the whole path, as
|
|
75
|
+
the ones of PromQL do, with the rest of it for the application in its group
|
|
76
|
+
``path``.
|
|
77
|
+
|
|
78
|
+
The code of this plugin is a good example for learning to build new plugins,
|
|
79
|
+
you're welcome to fork it.
|
|
80
|
+
|
|
81
|
+
See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
|
|
82
|
+
default.
|
|
83
|
+
|
|
84
|
+
See also
|
|
85
|
+
--------
|
|
86
|
+
|
|
87
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
88
|
+
|
|
89
|
+
Ported from `Mojolicious::Plugin::Mount
|
|
90
|
+
<https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/Mount.pm>`_ of Mojolicious 9.49.
|
|
91
|
+
|
|
92
|
+
Classes
|
|
93
|
+
-------
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
import Pyjo.App
|
|
97
|
+
import Pyjo.App.ASGI
|
|
98
|
+
import Pyjo.App.FastCGI
|
|
99
|
+
import Pyjo.App.WSGI
|
|
100
|
+
import Pyjo.Exception
|
|
101
|
+
import Pyjo.Server
|
|
102
|
+
import Pyjoyment.Plugin
|
|
103
|
+
import Pyjoyment.Routes.Pattern
|
|
104
|
+
|
|
105
|
+
import importlib
|
|
106
|
+
import inspect
|
|
107
|
+
import os
|
|
108
|
+
import re
|
|
109
|
+
|
|
110
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if TYPE_CHECKING:
|
|
114
|
+
import Pyjoyment
|
|
115
|
+
import Pyjoyment.Routes.Route
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Pyjoyment_Plugin_Mount(Pyjoyment.Plugin.object):
|
|
119
|
+
"""
|
|
120
|
+
:mod:`Pyjoyment.Plugin.Mount` inherits all methods from
|
|
121
|
+
:mod:`Pyjoyment.Plugin` and implements the following new ones.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def register(self, app: 'Pyjoyment.object', conf: dict[Any, Any]) -> 'Pyjoyment.Routes.Route.object':
|
|
125
|
+
"""::
|
|
126
|
+
|
|
127
|
+
route = plugin.register(Pyjoyment.new(), {'/foo': '/some/app.py'})
|
|
128
|
+
route = plugin.register(Pyjoyment.new(), {'/foo': 'some_app:app'})
|
|
129
|
+
|
|
130
|
+
Mount application and return the generated route, which is usually a
|
|
131
|
+
:mod:`Pyjoyment.Routes.Route` object.
|
|
132
|
+
"""
|
|
133
|
+
import Pyjoyment
|
|
134
|
+
|
|
135
|
+
key: str | re.Pattern[str] = next(iter(conf))
|
|
136
|
+
embed: Any = _load(conf[key])
|
|
137
|
+
if isinstance(embed, Pyjoyment.object):
|
|
138
|
+
embed.secrets = app.secrets
|
|
139
|
+
|
|
140
|
+
# Not in Mojolicious: another application of Pyjo, as one of Pyjo.App.WSGI, gets the server that runs it before it
|
|
141
|
+
# starts and after it stops, as the application that mounts it gets it
|
|
142
|
+
else:
|
|
143
|
+
|
|
144
|
+
def before_server_start(server: Pyjo.Server.object, app: Any) -> None:
|
|
145
|
+
embed.server(server)
|
|
146
|
+
|
|
147
|
+
def after_server_stop(server: Pyjo.Server.object, app: Any) -> Any:
|
|
148
|
+
return embed.server_stopped(server)
|
|
149
|
+
|
|
150
|
+
app.hook('before_server_start', before_server_start)
|
|
151
|
+
app.hook('after_server_stop', after_server_stop)
|
|
152
|
+
embed.log = app.log
|
|
153
|
+
|
|
154
|
+
# Not in Mojolicious: a regular expression that matches the whole path, as the ones of PromQL do, whose group
|
|
155
|
+
# path is the rest of the path for the application, as partial routes have it, and whose other named groups are
|
|
156
|
+
# captures
|
|
157
|
+
if isinstance(key, re.Pattern):
|
|
158
|
+
route = app.routes.any()
|
|
159
|
+
route.pattern = _Whole(key)
|
|
160
|
+
route.partial = True
|
|
161
|
+
return route.to(app=embed)
|
|
162
|
+
|
|
163
|
+
# Extract host
|
|
164
|
+
path: str | None = key
|
|
165
|
+
host = None
|
|
166
|
+
if m := re.match(r'^(\*\.)?([^/]+)(/.*)?$', path):
|
|
167
|
+
host = re.compile(rf'^(?:.*\.)?{re.escape(m[2])}$' if m[1] else rf'^{re.escape(m[2])}$', re.I)
|
|
168
|
+
path = m[3]
|
|
169
|
+
|
|
170
|
+
route = app.routes.any(path)
|
|
171
|
+
route.partial = True
|
|
172
|
+
route.to(app=embed)
|
|
173
|
+
return route.requires(host=host) if host else route
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Not in Mojolicious: an application as an object, or as the name of a module and its attribute, as ASGI servers take
|
|
177
|
+
# it, an ASGI or a WSGI application, which Pyjo.App.ASGI or Pyjo.App.WSGI runs, and the location of a FastCGI
|
|
178
|
+
# application, which Pyjo.App.FastCGI connects to, where Mojolicious loads a script
|
|
179
|
+
def _load(value: Any) -> Pyjo.Server.Application:
|
|
180
|
+
if isinstance(value, str) and re.match(r'fcgi(\+unix)?://', value):
|
|
181
|
+
return Pyjo.App.FastCGI.new(url=value)
|
|
182
|
+
if isinstance(value, str) and re.fullmatch(r'[\w.]+:[\w.]+', value) and not os.path.exists(value):
|
|
183
|
+
module, _, name = value.partition(':')
|
|
184
|
+
try:
|
|
185
|
+
value = importlib.import_module(module)
|
|
186
|
+
for attr in name.split('.'):
|
|
187
|
+
value = getattr(value, attr)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
raise Pyjo.Exception.new(f'Can\'t load application "{module}:{name}": {e}') from e
|
|
190
|
+
elif isinstance(value, str | os.PathLike):
|
|
191
|
+
return Pyjo.Server.new().load_app(cast(str, value))
|
|
192
|
+
|
|
193
|
+
if isinstance(value, Pyjo.App.object):
|
|
194
|
+
return value
|
|
195
|
+
if inspect.iscoroutinefunction(value) or inspect.iscoroutinefunction(type(value).__call__):
|
|
196
|
+
return Pyjo.App.ASGI.new(app=value)
|
|
197
|
+
if not callable(value):
|
|
198
|
+
raise Pyjo.Exception.new(f'"{value!r}" is not an application')
|
|
199
|
+
return Pyjo.App.WSGI.new(app=value)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Not in Mojolicious: the pattern of a regular expression that matches the whole path, whose named groups are captures,
|
|
203
|
+
# and whose group path, from its start to the end of the path, is the rest of the path
|
|
204
|
+
class _Whole(Pyjoyment.Routes.Pattern.object):
|
|
205
|
+
def __init__(self, whole: re.Pattern[str]) -> None:
|
|
206
|
+
super().__init__()
|
|
207
|
+
self.whole = whole
|
|
208
|
+
# The regular expression and its source, as the pages of exceptions and of not found and the command routes show
|
|
209
|
+
# them
|
|
210
|
+
self.regex = whole
|
|
211
|
+
self.unparsed = whole.pattern
|
|
212
|
+
|
|
213
|
+
def match_partial(self, path: str, detect: bool = False) -> tuple[dict[str, Any], str] | None:
|
|
214
|
+
if (m := self.whole.fullmatch(path)) is None:
|
|
215
|
+
return None
|
|
216
|
+
captures = {**self.defaults, **{name: value for name, value in m.groupdict().items() if value is not None}}
|
|
217
|
+
rest = path[m.start('path') :] if captures.pop('path', None) is not None else ''
|
|
218
|
+
return captures, rest
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
new = Pyjoyment_Plugin_Mount.new
|
|
222
|
+
object = Pyjoyment_Plugin_Mount
|
|
223
|
+
Mount = Pyjoyment_Plugin_Mount
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment.Plugin.PyRenderer - Template string renderer plugin
|
|
3
|
+
=============================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
# Pyjoyment
|
|
7
|
+
app.plugin('PyRenderer')
|
|
8
|
+
|
|
9
|
+
# A template of a function
|
|
10
|
+
@app.template('index.html')
|
|
11
|
+
def index(c, name):
|
|
12
|
+
return t'<p>Hello {name}!</p>'
|
|
13
|
+
|
|
14
|
+
c.render('index', name='Joe')
|
|
15
|
+
|
|
16
|
+
# An inline t-string of Python 3.14
|
|
17
|
+
c.render(inline=t'<p>Hello {name}!</p>')
|
|
18
|
+
|
|
19
|
+
# The same on any Python
|
|
20
|
+
c.render(inline=t('<p>Hello {name}!</p>', name=name))
|
|
21
|
+
|
|
22
|
+
:mod:`Pyjoyment.Plugin.PyRenderer` is a renderer for ``py`` templates, which
|
|
23
|
+
are template strings, t-strings of
|
|
24
|
+
`PEP 750 <https://peps.python.org/pep-0750/>`_ or of
|
|
25
|
+
:func:`Pyjo.String.Template.t`, rendered by :mod:`Pyjo.String.Template`, which
|
|
26
|
+
escapes the values that they interpolate, unless they are markup, as the
|
|
27
|
+
helpers of :mod:`Pyjoyment.Plugin.TagHelpers` return. ::
|
|
28
|
+
|
|
29
|
+
from Pyjo.String.Template import t
|
|
30
|
+
|
|
31
|
+
# "<p>Hello <script>!</p>"
|
|
32
|
+
name = '<script>'
|
|
33
|
+
c.render(inline=t('<p>Hello {name}!</p>', name=name))
|
|
34
|
+
|
|
35
|
+
# "<p><a href="/">Home</a></p>"
|
|
36
|
+
c.render(inline=t('<p>{link}</p>', link=c.link_to('Home', '/')))
|
|
37
|
+
|
|
38
|
+
A template string is code, which cannot be read from a file of data as a
|
|
39
|
+
template of :mod:`Pyjoyment.Plugin.EPRenderer` is, so a template is a function
|
|
40
|
+
that returns one, registered with :meth:`Pyjoyment.Pyjoyment_App.template`,
|
|
41
|
+
or the function ``template`` of :mod:`Pyjoyment.Lite`, by its name with the
|
|
42
|
+
format, as ``index.html``. The function gets the controller and the values
|
|
43
|
+
of the stash that it names as its parameters, as the variables of an ``ep``
|
|
44
|
+
template, or all of them with ``**``, and it can set the values of the stash
|
|
45
|
+
that the renderer reads, as a layout. ::
|
|
46
|
+
|
|
47
|
+
# A template with a layout
|
|
48
|
+
@app.template('hello.html')
|
|
49
|
+
def hello(c, name):
|
|
50
|
+
c.layout('default')
|
|
51
|
+
c.title('Hello')
|
|
52
|
+
return t'<p>Hello {name}!</p>'
|
|
53
|
+
|
|
54
|
+
# The layout, which gets the content and the title
|
|
55
|
+
@app.template('layouts/default.html')
|
|
56
|
+
def default(c, **stash):
|
|
57
|
+
return t'<title>{c.title()}</title><body>{c.content()}</body>'
|
|
58
|
+
|
|
59
|
+
A template can also be a file in
|
|
60
|
+
:attr:`Pyjoyment.Renderer.Pyjoyment_Renderer.paths` with the extension ``py``,
|
|
61
|
+
as ``templates/index.html.py``, a module of Python whose function ``render``
|
|
62
|
+
is the function of the template. The file is
|
|
63
|
+
compiled once and kept in the cache of the renderer, without the bytecode
|
|
64
|
+
that an import writes next to it, and an exception in it shows the lines of
|
|
65
|
+
the file. ::
|
|
66
|
+
|
|
67
|
+
# templates/hello.html.py
|
|
68
|
+
def render(c, name):
|
|
69
|
+
return t'<p>Hello {name}!</p>'
|
|
70
|
+
|
|
71
|
+
An inline template that is a template string is rendered by it, unless
|
|
72
|
+
another handler is given. Its values are evaluated where it is written, so it
|
|
73
|
+
gets the values of the stash as the code around it does, ``c.stash('name')``,
|
|
74
|
+
and a layout does not apply to it, as to any inline template.
|
|
75
|
+
|
|
76
|
+
This is a core plugin, that means it is always enabled and its code a good
|
|
77
|
+
example for learning to build new plugins, you're welcome to fork it.
|
|
78
|
+
|
|
79
|
+
See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
|
|
80
|
+
default.
|
|
81
|
+
|
|
82
|
+
See also
|
|
83
|
+
--------
|
|
84
|
+
|
|
85
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
86
|
+
|
|
87
|
+
Inspired by `Mojolicious::Plugin::EPLRenderer
|
|
88
|
+
<https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/EPLRenderer.pm>`_ of Mojolicious 9.49.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
import Pyjo.Exception
|
|
92
|
+
import Pyjo.File
|
|
93
|
+
import Pyjo.String.Template
|
|
94
|
+
import Pyjoyment.Plugin
|
|
95
|
+
|
|
96
|
+
from Pyjo.Util import decode, encode, md5_sum
|
|
97
|
+
|
|
98
|
+
import inspect
|
|
99
|
+
import io
|
|
100
|
+
import types
|
|
101
|
+
|
|
102
|
+
from collections.abc import Callable
|
|
103
|
+
from typing import TYPE_CHECKING, Any
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if TYPE_CHECKING:
|
|
107
|
+
import Pyjoyment
|
|
108
|
+
import Pyjoyment.Controller
|
|
109
|
+
import Pyjoyment.Renderer
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Pyjoyment_Plugin_PyRenderer(Pyjoyment.Plugin.object):
|
|
113
|
+
"""
|
|
114
|
+
:mod:`Pyjoyment.Plugin.PyRenderer` inherits all methods from
|
|
115
|
+
:mod:`Pyjoyment.Plugin` and implements the following new ones.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
|
|
119
|
+
"""::
|
|
120
|
+
|
|
121
|
+
plugin.register(Pyjoyment.new())
|
|
122
|
+
|
|
123
|
+
Register renderer in :mod:`Pyjoyment` application.
|
|
124
|
+
"""
|
|
125
|
+
app.renderer.add_handler('py', _handler)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _handler(
|
|
129
|
+
renderer: 'Pyjoyment.Renderer.object',
|
|
130
|
+
c: 'Pyjoyment.Controller.object',
|
|
131
|
+
output: io.StringIO,
|
|
132
|
+
options: dict[str, Any],
|
|
133
|
+
) -> None:
|
|
134
|
+
log = c.helpers.log()
|
|
135
|
+
|
|
136
|
+
# Inline
|
|
137
|
+
inline = options.get('inline')
|
|
138
|
+
if inline is not None:
|
|
139
|
+
if not Pyjo.String.Template.is_template(inline):
|
|
140
|
+
return
|
|
141
|
+
template = Pyjo.String.Template.t(inline)
|
|
142
|
+
name = md5_sum(encode('UTF-8', '{}'.join(template.strings)))
|
|
143
|
+
log.trace(f'Rendering inline template "{name}"')
|
|
144
|
+
output.write(template.render())
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if (name := renderer.template_name(options)) is None:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# Function
|
|
151
|
+
if (cb := renderer.templates.get(name.removesuffix('.py'))) is not None:
|
|
152
|
+
log.trace(f'Rendering template "{name}" of a function')
|
|
153
|
+
|
|
154
|
+
# File
|
|
155
|
+
elif (path := renderer.template_path(options)) is not None:
|
|
156
|
+
key = md5_sum(encode('UTF-8', f'py:{path}'))
|
|
157
|
+
if (cb := renderer.cache.get(key)) is not None:
|
|
158
|
+
log.trace(f'Rendering cached template "{name}"')
|
|
159
|
+
else:
|
|
160
|
+
log.trace(f'Rendering template "{name}"')
|
|
161
|
+
renderer.cache.set(key, cb := _load(path, key, renderer.encoding))
|
|
162
|
+
|
|
163
|
+
# No template
|
|
164
|
+
else:
|
|
165
|
+
log.debug(f'Template "{name}" not found')
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
output.write(Pyjo.String.Template.escape(_call(cb, c, c.stash())))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# The function "render" of a file, which is compiled from its source, as importlib would write its bytecode next to it,
|
|
172
|
+
# with the path of the file, from which a traceback reads the lines of the template
|
|
173
|
+
def _load(path: str, key: str, encoding: str | None) -> Callable[..., Any]:
|
|
174
|
+
data = Pyjo.File.path(path).slurp()
|
|
175
|
+
assert isinstance(data, bytes)
|
|
176
|
+
if encoding is None:
|
|
177
|
+
source = data.decode('latin-1')
|
|
178
|
+
elif (source := decode(encoding, data)) is None:
|
|
179
|
+
raise Pyjo.Exception.new(f'Template "{path}" has invalid encoding')
|
|
180
|
+
try:
|
|
181
|
+
code = compile(source, path, 'exec', dont_inherit=True)
|
|
182
|
+
except SyntaxError as e:
|
|
183
|
+
raise Pyjo.Exception.new(f'{e.msg} at {path} line {e.lineno}.\n') from None
|
|
184
|
+
|
|
185
|
+
module = types.ModuleType(f'{__name__}.{key}')
|
|
186
|
+
module.__file__ = path
|
|
187
|
+
|
|
188
|
+
# No spec, as a spec without a loader makes linecache warn, which then reads the lines from the file
|
|
189
|
+
del module.__spec__
|
|
190
|
+
exec(code, module.__dict__)
|
|
191
|
+
if not callable(cb := module.__dict__.get('render')):
|
|
192
|
+
raise Pyjo.Exception.new(f'Template "{path}" has no function "render"')
|
|
193
|
+
return cb
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# The controller and the values of the stash that the function names, or all of them for **
|
|
197
|
+
def _call(cb: Callable[..., Any], c: 'Pyjoyment.Controller.object', stash: dict[str, Any]) -> Any:
|
|
198
|
+
parameters = list(inspect.signature(cb).parameters.values())
|
|
199
|
+
rest = parameters[1:]
|
|
200
|
+
if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in rest):
|
|
201
|
+
values = {k: v for k, v in stash.items() if k.isidentifier() and k != parameters[0].name}
|
|
202
|
+
else:
|
|
203
|
+
values = {p.name: stash[p.name] for p in rest if p.name in stash}
|
|
204
|
+
return cb(c, **values)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
new = Pyjoyment_Plugin_PyRenderer.new
|
|
208
|
+
object = Pyjoyment_Plugin_PyRenderer
|
|
209
|
+
PyRenderer = Pyjoyment_Plugin_PyRenderer
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment.Plugin.TOMLConfig - TOML configuration plugin
|
|
3
|
+
=======================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
# myapp.toml (it's just TOML with embedded Python)
|
|
7
|
+
%# Just a value
|
|
8
|
+
foo = "bar"
|
|
9
|
+
|
|
10
|
+
%# Nested data structures are fine too
|
|
11
|
+
baz = ["♥"]
|
|
12
|
+
|
|
13
|
+
%# You have full access to the application
|
|
14
|
+
music_dir = '<%= app.home.child('music') %>'
|
|
15
|
+
|
|
16
|
+
[db]
|
|
17
|
+
host = "localhost"
|
|
18
|
+
|
|
19
|
+
# Pyjoyment
|
|
20
|
+
config = app.plugin('TOMLConfig')
|
|
21
|
+
print(config['foo'])
|
|
22
|
+
|
|
23
|
+
# Pyjoyment.Lite
|
|
24
|
+
config = plugin('TOMLConfig')
|
|
25
|
+
print(config['foo'])
|
|
26
|
+
|
|
27
|
+
# foo.html.ep
|
|
28
|
+
%= config()['foo']
|
|
29
|
+
|
|
30
|
+
# The configuration is available application-wide
|
|
31
|
+
config = app.config()
|
|
32
|
+
print(config['foo'])
|
|
33
|
+
|
|
34
|
+
# Everything can be customized with options
|
|
35
|
+
config = plugin('TOMLConfig', file='/etc/myapp.conf')
|
|
36
|
+
|
|
37
|
+
:mod:`Pyjoyment.Plugin.TOMLConfig` is a TOML configuration plugin that
|
|
38
|
+
preprocesses its input with :mod:`Pyjo.Template` and parses it with
|
|
39
|
+
:mod:`tomllib` of the standard library.
|
|
40
|
+
|
|
41
|
+
The application object can be accessed via ``app``. A default configuration
|
|
42
|
+
filename in the application home directory will be generated from the value
|
|
43
|
+
of :attr:`Pyjoyment.Pyjoyment_App.moniker` (``$moniker.toml``). You can extend
|
|
44
|
+
the normal configuration file ``$moniker.toml`` with ``mode`` specific ones
|
|
45
|
+
like ``$moniker.$mode.toml``, which will be detected automatically.
|
|
46
|
+
|
|
47
|
+
A path goes into a literal string of TOML, in single quotes, which keeps
|
|
48
|
+
the backslashes of a path of Windows, where a basic string in double quotes
|
|
49
|
+
takes them for escapes.
|
|
50
|
+
|
|
51
|
+
These configuration values are currently reserved:
|
|
52
|
+
|
|
53
|
+
``config_override``
|
|
54
|
+
If this configuration value has been set in
|
|
55
|
+
:meth:`Pyjoyment.Pyjoyment_App.config` when this plugin is loaded, it will
|
|
56
|
+
not do anything besides loading deployment specific plugins.
|
|
57
|
+
|
|
58
|
+
``plugins`` ::
|
|
59
|
+
|
|
60
|
+
plugins = [{ SetUserGroup = { user = "sri", group = "staff" } }]
|
|
61
|
+
|
|
62
|
+
One or more deployment specific plugins that should be loaded right after
|
|
63
|
+
this plugin has been loaded.
|
|
64
|
+
|
|
65
|
+
The code of this plugin is a good example for learning to build new plugins,
|
|
66
|
+
you're welcome to fork it.
|
|
67
|
+
|
|
68
|
+
See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
|
|
69
|
+
default.
|
|
70
|
+
|
|
71
|
+
Options
|
|
72
|
+
-------
|
|
73
|
+
|
|
74
|
+
:mod:`Pyjoyment.Plugin.TOMLConfig` inherits all options from
|
|
75
|
+
:mod:`Pyjoyment.Plugin.JSONConfig`.
|
|
76
|
+
|
|
77
|
+
See also
|
|
78
|
+
--------
|
|
79
|
+
|
|
80
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
81
|
+
|
|
82
|
+
Inspired by `Mojolicious::Plugin::NotYAMLConfig
|
|
83
|
+
<https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/NotYAMLConfig.pm>`_ of Mojolicious 9.49.
|
|
84
|
+
|
|
85
|
+
Classes
|
|
86
|
+
-------
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
import Pyjo.Exception
|
|
90
|
+
import Pyjoyment.Plugin.JSONConfig
|
|
91
|
+
|
|
92
|
+
import tomllib
|
|
93
|
+
|
|
94
|
+
from typing import TYPE_CHECKING, Any
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if TYPE_CHECKING:
|
|
98
|
+
import Pyjoyment
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Pyjoyment_Plugin_TOMLConfig(Pyjoyment.Plugin.JSONConfig.object):
|
|
102
|
+
"""
|
|
103
|
+
:mod:`Pyjoyment.Plugin.TOMLConfig` inherits all methods from
|
|
104
|
+
:mod:`Pyjoyment.Plugin.JSONConfig` and implements the following new ones.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def parse(self, content: str, file: str, conf: dict[str, Any], app: 'Pyjoyment.object') -> dict[str, Any]:
|
|
108
|
+
"""::
|
|
109
|
+
|
|
110
|
+
plugin.parse(content, file, conf, app)
|
|
111
|
+
|
|
112
|
+
Process content with
|
|
113
|
+
:meth:`~Pyjoyment.Plugin.JSONConfig.Pyjoyment_Plugin_JSONConfig.render`
|
|
114
|
+
and parse it with :mod:`tomllib`. ::
|
|
115
|
+
|
|
116
|
+
def parse(self, content, file, conf, app):
|
|
117
|
+
...
|
|
118
|
+
content = self.render(content, file, conf, app)
|
|
119
|
+
...
|
|
120
|
+
return config
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
return tomllib.loads(self.render(content, file, conf, app))
|
|
124
|
+
except Exception as e:
|
|
125
|
+
raise Pyjo.Exception.new(f'Can\'t load configuration from file "{file}": {e}') from e
|
|
126
|
+
|
|
127
|
+
def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
128
|
+
"""::
|
|
129
|
+
|
|
130
|
+
config = plugin.register(Pyjoyment.new())
|
|
131
|
+
config = plugin.register(Pyjoyment.new(), {'file': '/etc/foo.conf'})
|
|
132
|
+
|
|
133
|
+
Register plugin in :mod:`Pyjoyment` application and merge
|
|
134
|
+
configuration.
|
|
135
|
+
"""
|
|
136
|
+
return super().register(app, {'ext': 'toml', **(conf or {})})
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
new = Pyjoyment_Plugin_TOMLConfig.new
|
|
140
|
+
object = Pyjoyment_Plugin_TOMLConfig
|
|
141
|
+
TOMLConfig = Pyjoyment_Plugin_TOMLConfig
|