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
Pyjoyment/__init__.py
ADDED
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment - Real-time web framework
|
|
3
|
+
===================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
# Application
|
|
7
|
+
import Pyjoyment
|
|
8
|
+
|
|
9
|
+
class MyApp(Pyjoyment.object):
|
|
10
|
+
|
|
11
|
+
# Route
|
|
12
|
+
def startup(self):
|
|
13
|
+
self.get('/hello').to('foo#hello')
|
|
14
|
+
|
|
15
|
+
# Controller
|
|
16
|
+
import Pyjoyment.Controller
|
|
17
|
+
|
|
18
|
+
class MyApp_Controller_Foo(Pyjoyment.Controller.object):
|
|
19
|
+
|
|
20
|
+
# Action
|
|
21
|
+
def hello(self):
|
|
22
|
+
self.render(text='Hello World!')
|
|
23
|
+
|
|
24
|
+
An amazing real-time web framework built on top of the powerful :mod:`Pyjo`
|
|
25
|
+
web development toolkit, ported from `Mojolicious <https://mojolicious.org>`_.
|
|
26
|
+
With support for RESTful routes, plugins, content negotiation, session
|
|
27
|
+
management, static file server, first class Unicode support and much more for
|
|
28
|
+
you to discover.
|
|
29
|
+
|
|
30
|
+
Hooks
|
|
31
|
+
-----
|
|
32
|
+
|
|
33
|
+
:mod:`Pyjoyment` will emit the following hooks in the listed order.
|
|
34
|
+
|
|
35
|
+
before_command
|
|
36
|
+
Emitted right before the application runs a command through the command
|
|
37
|
+
line interface. ::
|
|
38
|
+
|
|
39
|
+
@app.hook('before_command')
|
|
40
|
+
def before_command(command, args): ...
|
|
41
|
+
|
|
42
|
+
Useful for reconfiguring the application before running a command or to
|
|
43
|
+
modify the behavior of a command. (Passed the command object and the list
|
|
44
|
+
of the command arguments)
|
|
45
|
+
|
|
46
|
+
before_server_start
|
|
47
|
+
Emitted right before the application server is started, for web servers
|
|
48
|
+
that support it, which includes all the built-in ones. ::
|
|
49
|
+
|
|
50
|
+
@app.hook('before_server_start')
|
|
51
|
+
def before_server_start(server, app): ...
|
|
52
|
+
|
|
53
|
+
Useful for reconfiguring application servers dynamically or collecting
|
|
54
|
+
server diagnostics information. (Passed the server and application
|
|
55
|
+
objects)
|
|
56
|
+
|
|
57
|
+
after_build_tx
|
|
58
|
+
Emitted right after the transaction is built and before the HTTP request
|
|
59
|
+
gets parsed. ::
|
|
60
|
+
|
|
61
|
+
@app.hook('after_build_tx')
|
|
62
|
+
def after_build_tx(tx, app): ...
|
|
63
|
+
|
|
64
|
+
This is a very powerful hook and should not be used lightly, it makes some
|
|
65
|
+
rather advanced features such as upload progress bars possible. Note that
|
|
66
|
+
this hook will not work for embedded applications, because only the host
|
|
67
|
+
application gets to build transactions. (Passed the transaction and
|
|
68
|
+
application objects)
|
|
69
|
+
|
|
70
|
+
around_dispatch
|
|
71
|
+
Emitted right after a new request has been received and wraps around the
|
|
72
|
+
whole dispatch process, so you have to manually forward to the next hook
|
|
73
|
+
if you want to continue the chain. Default exception handling with the
|
|
74
|
+
helper ``reply.exception`` of :mod:`Pyjoyment.Plugin.DefaultHelpers` is
|
|
75
|
+
the first hook in the chain and a call to :meth:`Pyjoyment_App.dispatch`
|
|
76
|
+
the last, yours will be in between. ::
|
|
77
|
+
|
|
78
|
+
@app.hook('around_dispatch')
|
|
79
|
+
def around_dispatch(next, c):
|
|
80
|
+
...
|
|
81
|
+
next()
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
This is a very powerful hook and should not be used lightly, it allows
|
|
85
|
+
you, for example, to customize application-wide exception handling,
|
|
86
|
+
consider it the sledgehammer in your toolbox. (Passed a callback leading
|
|
87
|
+
to the next hook and the default controller object)
|
|
88
|
+
|
|
89
|
+
before_dispatch
|
|
90
|
+
Emitted right before the static file server and router start their work. ::
|
|
91
|
+
|
|
92
|
+
@app.hook('before_dispatch')
|
|
93
|
+
def before_dispatch(c): ...
|
|
94
|
+
|
|
95
|
+
Very useful for rewriting incoming requests and other preprocessing
|
|
96
|
+
tasks. (Passed the default controller object)
|
|
97
|
+
|
|
98
|
+
after_static
|
|
99
|
+
Emitted after a static file response has been generated by the static
|
|
100
|
+
file server. ::
|
|
101
|
+
|
|
102
|
+
@app.hook('after_static')
|
|
103
|
+
def after_static(c): ...
|
|
104
|
+
|
|
105
|
+
Mostly used for post-processing static file responses. (Passed the
|
|
106
|
+
default controller object)
|
|
107
|
+
|
|
108
|
+
before_routes
|
|
109
|
+
Emitted after the static file server determined if a static file should
|
|
110
|
+
be served and before the router starts its work. ::
|
|
111
|
+
|
|
112
|
+
@app.hook('before_routes')
|
|
113
|
+
def before_routes(c): ...
|
|
114
|
+
|
|
115
|
+
Mostly used for custom dispatchers and collecting metrics. (Passed the
|
|
116
|
+
default controller object)
|
|
117
|
+
|
|
118
|
+
around_action
|
|
119
|
+
Emitted right before an action gets executed and wraps around it, so you
|
|
120
|
+
have to manually forward to the next hook if you want to continue the
|
|
121
|
+
chain. Default action dispatching is the last hook in the chain, yours
|
|
122
|
+
will run before it. ::
|
|
123
|
+
|
|
124
|
+
@app.hook('around_action')
|
|
125
|
+
def around_action(next, c, action, last):
|
|
126
|
+
...
|
|
127
|
+
return next()
|
|
128
|
+
|
|
129
|
+
This is a very powerful hook and should not be used lightly, it allows you
|
|
130
|
+
for example to pass additional arguments to actions or handle return
|
|
131
|
+
values differently. Note that this hook can trigger more than once for the
|
|
132
|
+
same request if there are nested routes. (Passed a callback leading to the
|
|
133
|
+
next hook, the current controller object, the action callback and a flag
|
|
134
|
+
indicating if this action is an endpoint)
|
|
135
|
+
|
|
136
|
+
before_render
|
|
137
|
+
Emitted before content is generated by the renderer. Note that this hook
|
|
138
|
+
can trigger out of order due to its dynamic nature, and with embedded
|
|
139
|
+
applications will only work for the application that is rendering. ::
|
|
140
|
+
|
|
141
|
+
@app.hook('before_render')
|
|
142
|
+
def before_render(c, args): ...
|
|
143
|
+
|
|
144
|
+
Mostly used for pre-processing arguments passed to the renderer. (Passed
|
|
145
|
+
the current controller object and the render arguments)
|
|
146
|
+
|
|
147
|
+
after_render
|
|
148
|
+
Emitted after content has been generated by the renderer that will be
|
|
149
|
+
assigned to the response. Note that this hook can trigger out of order due
|
|
150
|
+
to its dynamic nature, and with embedded applications will only work for
|
|
151
|
+
the application that is rendering. ::
|
|
152
|
+
|
|
153
|
+
@app.hook('after_render')
|
|
154
|
+
def after_render(c, output, format): ...
|
|
155
|
+
|
|
156
|
+
Mostly used for post-processing dynamically generated content. (Passed
|
|
157
|
+
the current controller object, a file object with the content and the
|
|
158
|
+
format)
|
|
159
|
+
|
|
160
|
+
after_dispatch
|
|
161
|
+
Emitted in reverse order after a response has been generated. Note that
|
|
162
|
+
this hook can trigger out of order due to its dynamic nature, and with
|
|
163
|
+
embedded applications will only work for the application that is
|
|
164
|
+
generating the response. ::
|
|
165
|
+
|
|
166
|
+
@app.hook('after_dispatch')
|
|
167
|
+
def after_dispatch(c): ...
|
|
168
|
+
|
|
169
|
+
Useful for rewriting outgoing responses and other post-processing tasks.
|
|
170
|
+
(Passed the current controller object)
|
|
171
|
+
|
|
172
|
+
after_server_stop
|
|
173
|
+
Emitted right after the application server has stopped gracefully, in
|
|
174
|
+
each process and thread that has run the application, for web servers
|
|
175
|
+
that support it, which includes all the built-in ones but
|
|
176
|
+
:mod:`Pyjo.Server.WSGI` and :mod:`Pyjo.Server.Lambda`, which do not know
|
|
177
|
+
when they stop. The event loop no longer runs, and the server runs it
|
|
178
|
+
until a :mod:`Pyjo.Promise` that the hook returns is settled. ::
|
|
179
|
+
|
|
180
|
+
@app.hook('after_server_stop')
|
|
181
|
+
def after_server_stop(server, app): ...
|
|
182
|
+
|
|
183
|
+
Useful for cleaning up what the application has started for the
|
|
184
|
+
server, as the connections of a pool. (Passed the server and application
|
|
185
|
+
objects)
|
|
186
|
+
|
|
187
|
+
Helpers
|
|
188
|
+
-------
|
|
189
|
+
|
|
190
|
+
In addition to the attributes and methods below you can also call helpers on
|
|
191
|
+
:mod:`Pyjoyment` objects. This includes all helpers from
|
|
192
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` and :mod:`Pyjoyment.Plugin.TagHelpers`.
|
|
193
|
+
Note that application helpers are always called with a new default controller
|
|
194
|
+
object, so they can't depend on or change controller state, which includes
|
|
195
|
+
request, response and stash. ::
|
|
196
|
+
|
|
197
|
+
# Call helper
|
|
198
|
+
print(app.dumper({'foo': 'bar'}))
|
|
199
|
+
|
|
200
|
+
# Longer version
|
|
201
|
+
print(app.build_controller().helpers.dumper({'foo': 'bar'}))
|
|
202
|
+
|
|
203
|
+
See also
|
|
204
|
+
--------
|
|
205
|
+
|
|
206
|
+
:mod:`Pyjo`, https://mojolicious.org.
|
|
207
|
+
|
|
208
|
+
Ported from `Mojolicious <https://github.com/mojolicious/mojo/blob/v9.53/lib/Mojolicious.pm>`_ of Mojolicious 9.53.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
# Not in Mojolicious: a namespace that other distributions can add modules to, open before the package imports its
|
|
212
|
+
# subpackages, as Pyjoyment.Plugin and Pyjoyment.Command, which are namespaces that find their parts in it
|
|
213
|
+
from pkgutil import extend_path
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
__path__ = extend_path(__path__, __name__)
|
|
217
|
+
|
|
218
|
+
import Pyjo.App
|
|
219
|
+
import Pyjo.Loader
|
|
220
|
+
import Pyjo.Log
|
|
221
|
+
import Pyjo.Promise
|
|
222
|
+
import Pyjo.Server
|
|
223
|
+
import Pyjo.Transaction.HTTP
|
|
224
|
+
import Pyjo.Util
|
|
225
|
+
import Pyjoyment.Commands
|
|
226
|
+
import Pyjoyment.Controller
|
|
227
|
+
import Pyjoyment.Plugins
|
|
228
|
+
import Pyjoyment.Renderer
|
|
229
|
+
import Pyjoyment.Routes
|
|
230
|
+
import Pyjoyment.Routes.Pattern
|
|
231
|
+
import Pyjoyment.Routes.Route
|
|
232
|
+
import Pyjoyment.Sessions
|
|
233
|
+
import Pyjoyment.Static
|
|
234
|
+
import Pyjoyment.Types
|
|
235
|
+
import Pyjoyment.Validator
|
|
236
|
+
|
|
237
|
+
import functools
|
|
238
|
+
import inspect
|
|
239
|
+
import os
|
|
240
|
+
import sys
|
|
241
|
+
|
|
242
|
+
from collections.abc import Callable
|
|
243
|
+
from typing import TYPE_CHECKING, Any, Self, cast, overload
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if TYPE_CHECKING:
|
|
247
|
+
import Pyjoyment.Plugin.DefaultHelpers
|
|
248
|
+
import Pyjoyment.Plugin.TagHelpers
|
|
249
|
+
|
|
250
|
+
# Not in Mojolicious: the types of the helpers of the default plugins, which type checkers take from here, while
|
|
251
|
+
# __getattr__ finds the helpers in the renderer at run time
|
|
252
|
+
class _Base(Pyjo.App.object, Pyjoyment.Plugin.DefaultHelpers.Helpers, Pyjoyment.Plugin.TagHelpers.Helpers): ...
|
|
253
|
+
|
|
254
|
+
else:
|
|
255
|
+
_Base = Pyjo.App.object
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class Pyjoyment_App(_Base):
|
|
259
|
+
"""
|
|
260
|
+
:mod:`Pyjoyment` inherits all attributes and methods from :mod:`Pyjo.App`
|
|
261
|
+
and implements the following new ones.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
_stashes = ('config', 'defaults')
|
|
265
|
+
|
|
266
|
+
# Left out from Mojolicious: $CODENAME and $VERSION, as the version of the distribution is in its metadata
|
|
267
|
+
|
|
268
|
+
def __init__(self, attrs: dict[str, Any] | None = None, /, **kwargs: Any) -> None:
|
|
269
|
+
"""::
|
|
270
|
+
|
|
271
|
+
app = Pyjoyment.new()
|
|
272
|
+
app = Pyjoyment.new(moniker='foo_bar')
|
|
273
|
+
app = Pyjoyment.new({'moniker': 'foo_bar'})
|
|
274
|
+
|
|
275
|
+
Construct a new :mod:`Pyjoyment` application and call
|
|
276
|
+
:meth:`startup`. Will automatically detect your home directory. Also
|
|
277
|
+
sets up the renderer, a default set of plugins and an
|
|
278
|
+
``around_dispatch`` hook with the default exception handling.
|
|
279
|
+
"""
|
|
280
|
+
self._commands: Pyjoyment.Commands.object | None = None
|
|
281
|
+
|
|
282
|
+
self.controller_class: type[Pyjoyment.Controller.object] | str = Pyjoyment.Controller.object
|
|
283
|
+
"""::
|
|
284
|
+
|
|
285
|
+
cls = app.controller_class
|
|
286
|
+
app.controller_class = Pyjoyment.Controller.object
|
|
287
|
+
|
|
288
|
+
Class to be used for the default controller, defaults to
|
|
289
|
+
:mod:`Pyjoyment.Controller`.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
self.exception_format = 'html'
|
|
293
|
+
"""::
|
|
294
|
+
|
|
295
|
+
format = app.exception_format
|
|
296
|
+
app.exception_format = 'txt'
|
|
297
|
+
|
|
298
|
+
Format for HTTP exceptions (``html``, ``json``, or ``txt``), defaults to
|
|
299
|
+
``html``.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
self.max_request_size: int | None = None
|
|
303
|
+
"""::
|
|
304
|
+
|
|
305
|
+
max = app.max_request_size
|
|
306
|
+
app.max_request_size = 16777216
|
|
307
|
+
|
|
308
|
+
Maximum request size in bytes, defaults to the value of
|
|
309
|
+
:attr:`Pyjo.Message.Pyjo_Message.max_message_size`. Setting the value
|
|
310
|
+
to ``0`` will allow requests of indefinite size. Note that increasing
|
|
311
|
+
this value can also drastically increase memory usage, should you for
|
|
312
|
+
example attempt to parse an excessively large request body with the
|
|
313
|
+
methods ``dom`` or ``json`` of :mod:`Pyjo.Message`.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
self.plugins: Pyjoyment.Plugins.object = Pyjoyment.Plugins.new()
|
|
317
|
+
"""::
|
|
318
|
+
|
|
319
|
+
plugins = app.plugins
|
|
320
|
+
app.plugins = Pyjoyment.Plugins.new()
|
|
321
|
+
|
|
322
|
+
The plugin manager, defaults to a :mod:`Pyjoyment.Plugins` object. See
|
|
323
|
+
the :meth:`plugin` method below if you want to load a plugin. ::
|
|
324
|
+
|
|
325
|
+
# Add another namespace to load plugins from
|
|
326
|
+
app.plugins.namespaces.append('MyApp.Plugin')
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
self.preload_namespaces: list[str] = []
|
|
330
|
+
"""::
|
|
331
|
+
|
|
332
|
+
namespaces = app.preload_namespaces
|
|
333
|
+
app.preload_namespaces = ['MyApp.Controller']
|
|
334
|
+
|
|
335
|
+
Namespaces to preload classes from during application startup.
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
self.renderer: Pyjoyment.Renderer.object = Pyjoyment.Renderer.new()
|
|
339
|
+
"""::
|
|
340
|
+
|
|
341
|
+
renderer = app.renderer
|
|
342
|
+
app.renderer = Pyjoyment.Renderer.new()
|
|
343
|
+
|
|
344
|
+
Used to render content, defaults to a :mod:`Pyjoyment.Renderer` object.
|
|
345
|
+
::
|
|
346
|
+
|
|
347
|
+
# Enable compression
|
|
348
|
+
app.renderer.compress = True
|
|
349
|
+
|
|
350
|
+
# Add another "templates" directory
|
|
351
|
+
app.renderer.paths.append('/home/sri/templates')
|
|
352
|
+
|
|
353
|
+
# Add another "templates" directory with higher precedence
|
|
354
|
+
app.renderer.paths.insert(0, '/home/sri/themes/blue/templates')
|
|
355
|
+
|
|
356
|
+
# Add another class with templates in DATA section
|
|
357
|
+
app.renderer.classes.append('Pyjoyment.Plugin.Fun')
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
self.routes: Pyjoyment.Routes.object = Pyjoyment.Routes.new()
|
|
361
|
+
"""::
|
|
362
|
+
|
|
363
|
+
routes = app.routes
|
|
364
|
+
app.routes = Pyjoyment.Routes.new()
|
|
365
|
+
|
|
366
|
+
The router, defaults to a :mod:`Pyjoyment.Routes` object. You use this
|
|
367
|
+
in your startup method to define the url endpoints for your
|
|
368
|
+
application. ::
|
|
369
|
+
|
|
370
|
+
# Add routes
|
|
371
|
+
r = app.routes
|
|
372
|
+
r.get('/foo/bar').to('test#foo', title='Hello Pyjo!')
|
|
373
|
+
r.post('/baz').to('test#baz')
|
|
374
|
+
|
|
375
|
+
# Add another namespace to load controllers from
|
|
376
|
+
app.routes.namespaces.append('MyApp.MyController')
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
self.sessions: Pyjoyment.Sessions.object = Pyjoyment.Sessions.new()
|
|
380
|
+
"""::
|
|
381
|
+
|
|
382
|
+
sessions = app.sessions
|
|
383
|
+
app.sessions = Pyjoyment.Sessions.new()
|
|
384
|
+
|
|
385
|
+
Signed cookie based session manager, defaults to a
|
|
386
|
+
:mod:`Pyjoyment.Sessions` object. You can usually leave this alone, see
|
|
387
|
+
:meth:`Pyjoyment.Controller.Pyjoyment_Controller.session` for more
|
|
388
|
+
information about working with session data. ::
|
|
389
|
+
|
|
390
|
+
# Change name of cookie used for all sessions
|
|
391
|
+
app.sessions.cookie_name = 'mysession'
|
|
392
|
+
|
|
393
|
+
# Disable SameSite feature
|
|
394
|
+
app.sessions.samesite = None
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
self.static: Pyjoyment.Static.object = Pyjoyment.Static.new()
|
|
398
|
+
"""::
|
|
399
|
+
|
|
400
|
+
static = app.static
|
|
401
|
+
app.static = Pyjoyment.Static.new()
|
|
402
|
+
|
|
403
|
+
For serving static files from your ``public`` directories, defaults to
|
|
404
|
+
a :mod:`Pyjoyment.Static` object. ::
|
|
405
|
+
|
|
406
|
+
# Serve static files only with a "/static" prefix
|
|
407
|
+
app.static.prefix = '/static'
|
|
408
|
+
|
|
409
|
+
# Add another "public" directory
|
|
410
|
+
app.static.paths.append('/home/sri/public')
|
|
411
|
+
|
|
412
|
+
# Add another "public" directory with higher precedence
|
|
413
|
+
app.static.paths.insert(0, '/home/sri/themes/blue/public')
|
|
414
|
+
|
|
415
|
+
# Add another class with static files in DATA section
|
|
416
|
+
app.static.classes.append('Pyjoyment.Plugin.Fun')
|
|
417
|
+
|
|
418
|
+
# Remove built-in favicon
|
|
419
|
+
del app.static.extra['favicon.ico']
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
self.types: Pyjoyment.Types.object = Pyjoyment.Types.new()
|
|
423
|
+
"""::
|
|
424
|
+
|
|
425
|
+
types = app.types
|
|
426
|
+
app.types = Pyjoyment.Types.new()
|
|
427
|
+
|
|
428
|
+
Responsible for connecting file extensions with MIME types, defaults to
|
|
429
|
+
a :mod:`Pyjoyment.Types` object. ::
|
|
430
|
+
|
|
431
|
+
# Add custom MIME type
|
|
432
|
+
app.types.type('twt', 'text/tweet')
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
self.validator: Pyjoyment.Validator.object = Pyjoyment.Validator.new()
|
|
436
|
+
"""::
|
|
437
|
+
|
|
438
|
+
validator = app.validator
|
|
439
|
+
app.validator = Pyjoyment.Validator.new()
|
|
440
|
+
|
|
441
|
+
Validate values, defaults to a :mod:`Pyjoyment.Validator` object. ::
|
|
442
|
+
|
|
443
|
+
# Add validation check
|
|
444
|
+
app.validator.add_check('foo', lambda v, name, value: value != 'foo')
|
|
445
|
+
|
|
446
|
+
# Add validation filter
|
|
447
|
+
app.validator.add_filter('quotemeta', lambda v, name, value: re.escape(value))
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
self._mode: str | None = None
|
|
451
|
+
self._secrets: list[str] | None = None
|
|
452
|
+
self._dispatch = False
|
|
453
|
+
|
|
454
|
+
super().__init__(attrs, **kwargs)
|
|
455
|
+
|
|
456
|
+
home = self.home
|
|
457
|
+
self.renderer.paths.append(home.child('templates').to_str())
|
|
458
|
+
self.static.paths.append(home.child('public').to_str())
|
|
459
|
+
|
|
460
|
+
# Default to controller and application namespace
|
|
461
|
+
package = Pyjo.App._package(type(self)) # pyright: ignore[reportPrivateUsage]
|
|
462
|
+
controller = f'{package}.Controller'
|
|
463
|
+
self.preload_namespaces = [controller]
|
|
464
|
+
self.routes.namespaces = [controller, package]
|
|
465
|
+
|
|
466
|
+
for plugin in ('HeaderCondition', 'DefaultHelpers', 'TagHelpers', 'EPLRenderer', 'EPRenderer'):
|
|
467
|
+
self.plugin(plugin)
|
|
468
|
+
|
|
469
|
+
# Not in Mojolicious: the renderer of template strings
|
|
470
|
+
self.plugin('PyRenderer')
|
|
471
|
+
|
|
472
|
+
# Exception handling should be first in chain
|
|
473
|
+
self.hook('around_dispatch', _exception)
|
|
474
|
+
|
|
475
|
+
self.startup()
|
|
476
|
+
self.warmup()
|
|
477
|
+
|
|
478
|
+
# Unlike Mojolicious: Mojo registers the helpers with Mojo::DynamicMethods, Python looks them up in the renderer
|
|
479
|
+
# when a method is not found
|
|
480
|
+
def __getattr__(self, name: str) -> Any:
|
|
481
|
+
renderer: Any = vars(self).get('renderer')
|
|
482
|
+
if not name.startswith('_') and renderer is not None and renderer.get_helper(name) is not None:
|
|
483
|
+
return getattr(self.build_controller(), name)
|
|
484
|
+
package = f'{type(self).__module__}.{type(self).__qualname__}'
|
|
485
|
+
|
|
486
|
+
# The location of the caller, which croak adds to the message, the one of the attribute for the type of a helper
|
|
487
|
+
caller = sys._getframe(1) # pyright: ignore[reportPrivateUsage]
|
|
488
|
+
if caller.f_code is Pyjoyment.Controller._HELPER_GET and caller.f_back is not None: # pyright: ignore[reportPrivateUsage]
|
|
489
|
+
caller = caller.f_back
|
|
490
|
+
at = f'at {caller.f_code.co_filename} line {caller.f_lineno}'
|
|
491
|
+
|
|
492
|
+
# Not in Mojolicious: the attribute or the helper that the name most likely meant
|
|
493
|
+
mean = ''
|
|
494
|
+
if not name.startswith('_'):
|
|
495
|
+
names = [*dir(self), *(h.split('.')[0] for h in renderer.helpers)] if renderer is not None else []
|
|
496
|
+
if (suggestion := Pyjo.Util._suggest(name, names)) is not None: # pyright: ignore[reportPrivateUsage]
|
|
497
|
+
mean = f' Did you mean "{suggestion}"?'
|
|
498
|
+
raise AttributeError(
|
|
499
|
+
f'Can\'t locate object method "{name}" via package "{package}" {at}.{mean}\n', name=name, obj=self
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
@property
|
|
503
|
+
def commands(self) -> Pyjoyment.Commands.object:
|
|
504
|
+
"""::
|
|
505
|
+
|
|
506
|
+
commands = app.commands
|
|
507
|
+
app.commands = Pyjoyment.Commands.new()
|
|
508
|
+
|
|
509
|
+
Command line interface for your application, defaults to a
|
|
510
|
+
:mod:`Pyjoyment.Commands` object. ::
|
|
511
|
+
|
|
512
|
+
# Add another namespace to load commands from
|
|
513
|
+
app.commands.namespaces.append('MyApp.Command')
|
|
514
|
+
"""
|
|
515
|
+
if self._commands is None:
|
|
516
|
+
self._commands = Pyjoyment.Commands.new(app=self)
|
|
517
|
+
return self._commands
|
|
518
|
+
|
|
519
|
+
@commands.setter
|
|
520
|
+
def commands(self, value: Pyjoyment.Commands.object) -> None:
|
|
521
|
+
self._commands = value
|
|
522
|
+
|
|
523
|
+
@property
|
|
524
|
+
def log(self) -> Pyjo.Log.object:
|
|
525
|
+
"""::
|
|
526
|
+
|
|
527
|
+
log = app.log
|
|
528
|
+
app.log = Pyjo.Log.new()
|
|
529
|
+
|
|
530
|
+
The logging layer of your application, defaults to a :mod:`Pyjo.Log`
|
|
531
|
+
object. The level will default to either the ``PYJO_LOG_LEVEL``
|
|
532
|
+
environment variable, ``trace`` if the :attr:`mode` is
|
|
533
|
+
``development``, or ``info`` otherwise. All messages will be written to
|
|
534
|
+
``sys.stderr`` by default. ::
|
|
535
|
+
|
|
536
|
+
# Log debug message
|
|
537
|
+
app.log.debug('It works')
|
|
538
|
+
"""
|
|
539
|
+
if self._log is None:
|
|
540
|
+
# Reduced log output outside of development mode
|
|
541
|
+
log = Pyjo.Log.new()
|
|
542
|
+
if level := os.environ.get('PYJO_LOG_LEVEL'):
|
|
543
|
+
log.level = level
|
|
544
|
+
elif self.mode != 'development':
|
|
545
|
+
log.level = 'info'
|
|
546
|
+
self._log = log
|
|
547
|
+
return self._log
|
|
548
|
+
|
|
549
|
+
@log.setter
|
|
550
|
+
def log(self, value: Pyjo.Log.object) -> None:
|
|
551
|
+
self._log = value
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def mode(self) -> str:
|
|
555
|
+
"""::
|
|
556
|
+
|
|
557
|
+
mode = app.mode
|
|
558
|
+
app.mode = 'production'
|
|
559
|
+
|
|
560
|
+
The operating mode for your application, defaults to a value from the
|
|
561
|
+
``PYJO_MODE`` environment variable or ``development``.
|
|
562
|
+
"""
|
|
563
|
+
# Left out from Mojolicious: PLACK_ENV, the mode of Plack, which Python has no counterpart of
|
|
564
|
+
if self._mode is None:
|
|
565
|
+
self._mode = os.environ.get('PYJO_MODE') or 'development'
|
|
566
|
+
return self._mode
|
|
567
|
+
|
|
568
|
+
@mode.setter
|
|
569
|
+
def mode(self, value: str) -> None:
|
|
570
|
+
self._mode = value
|
|
571
|
+
|
|
572
|
+
@property
|
|
573
|
+
def secrets(self) -> list[str]:
|
|
574
|
+
"""::
|
|
575
|
+
|
|
576
|
+
secrets = app.secrets
|
|
577
|
+
app.secrets = [passphrase]
|
|
578
|
+
|
|
579
|
+
Secret passphrases used for signed cookies and the like, defaults to the
|
|
580
|
+
:attr:`moniker` of this application, which is not very secure, so you
|
|
581
|
+
should change it!!! As long as you are using the insecure default there
|
|
582
|
+
will be debug messages in the log file reminding you to change your
|
|
583
|
+
passphrase. Only the first passphrase is used to create new signatures,
|
|
584
|
+
but all of them for verification. So you can increase security without
|
|
585
|
+
invalidating all your existing signed cookies by rotating passphrases,
|
|
586
|
+
just add new ones to the front and remove old ones from the back. ::
|
|
587
|
+
|
|
588
|
+
# Rotate passphrases
|
|
589
|
+
app.secrets = ['new_passw0rd', 'old_passw0rd', 'very_old_passw0rd']
|
|
590
|
+
"""
|
|
591
|
+
if self._secrets is None:
|
|
592
|
+
# Warn developers about insecure default
|
|
593
|
+
self.log.trace('Your secret passphrase needs to be changed (see FAQ for more)')
|
|
594
|
+
|
|
595
|
+
# Default to moniker
|
|
596
|
+
self._secrets = [self.moniker]
|
|
597
|
+
return self._secrets
|
|
598
|
+
|
|
599
|
+
@secrets.setter
|
|
600
|
+
def secrets(self, value: list[str]) -> None:
|
|
601
|
+
self._secrets = value
|
|
602
|
+
|
|
603
|
+
def any(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
604
|
+
"""::
|
|
605
|
+
|
|
606
|
+
route = app.any('/:foo', lambda c: ...)
|
|
607
|
+
|
|
608
|
+
Generate route with
|
|
609
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.any` on
|
|
610
|
+
:attr:`routes`, takes the same arguments.
|
|
611
|
+
"""
|
|
612
|
+
return self.routes.any(*args, **constraints)
|
|
613
|
+
|
|
614
|
+
def build_controller(self, tx: Any = None) -> Pyjoyment.Controller.object:
|
|
615
|
+
"""::
|
|
616
|
+
|
|
617
|
+
c = app.build_controller()
|
|
618
|
+
c = app.build_controller(Pyjo.Transaction.HTTP.new())
|
|
619
|
+
c = app.build_controller(Pyjoyment.Controller.new())
|
|
620
|
+
|
|
621
|
+
Build default controller object with :attr:`controller_class`. ::
|
|
622
|
+
|
|
623
|
+
# Render template from application
|
|
624
|
+
foo = app.build_controller().render_to_string(template='foo')
|
|
625
|
+
"""
|
|
626
|
+
# Embedded application
|
|
627
|
+
stash: dict[str, Any] = {}
|
|
628
|
+
if tx is not None and isinstance(tx, Pyjoyment.Controller.object):
|
|
629
|
+
stash, tx = tx.stash(), tx.tx
|
|
630
|
+
|
|
631
|
+
# Build default controller
|
|
632
|
+
stash.update(self.defaults())
|
|
633
|
+
cls = self.controller_class
|
|
634
|
+
if isinstance(cls, str):
|
|
635
|
+
cls = Pyjo.Loader._class(cls) or Pyjoyment.Controller.object # pyright: ignore[reportPrivateUsage]
|
|
636
|
+
cls = cast(type[Pyjoyment.Controller.object], cls)
|
|
637
|
+
c = cls.new(app=self, stash=stash, tx=tx)
|
|
638
|
+
if c.tx is None:
|
|
639
|
+
# The controller keeps the transaction, as Mojo keeps it in the hash of the controller
|
|
640
|
+
c._own_tx = own = self.build_tx() # pyright: ignore[reportPrivateUsage]
|
|
641
|
+
c.tx = own
|
|
642
|
+
|
|
643
|
+
return c
|
|
644
|
+
|
|
645
|
+
def build_tx(self) -> Pyjo.Transaction.HTTP.object:
|
|
646
|
+
"""::
|
|
647
|
+
|
|
648
|
+
tx = app.build_tx()
|
|
649
|
+
|
|
650
|
+
Build :mod:`Pyjo.Transaction.HTTP` object and emit ``after_build_tx``
|
|
651
|
+
hook.
|
|
652
|
+
"""
|
|
653
|
+
tx = Pyjo.Transaction.HTTP.new()
|
|
654
|
+
if (max := self.max_request_size) is not None:
|
|
655
|
+
tx.req.max_message_size = max
|
|
656
|
+
self.plugins.emit_hook('after_build_tx', tx, self)
|
|
657
|
+
|
|
658
|
+
return tx
|
|
659
|
+
|
|
660
|
+
# defaults(**values) with no values is defaults(), which the first overload types
|
|
661
|
+
@overload
|
|
662
|
+
def defaults(self) -> dict[str, Any]: ... # pyright: ignore[reportOverlappingOverload]
|
|
663
|
+
|
|
664
|
+
@overload
|
|
665
|
+
def defaults(self, name: str, /) -> Any: ...
|
|
666
|
+
|
|
667
|
+
@overload
|
|
668
|
+
def defaults(self, values: dict[str, Any], /) -> Self: ...
|
|
669
|
+
|
|
670
|
+
@overload
|
|
671
|
+
def defaults(self, **values: Any) -> Self: ...
|
|
672
|
+
|
|
673
|
+
def defaults(self, *args: Any, **values: Any) -> Any:
|
|
674
|
+
"""::
|
|
675
|
+
|
|
676
|
+
hash = app.defaults()
|
|
677
|
+
foo = app.defaults('foo')
|
|
678
|
+
app = app.defaults({'foo': 'bar', 'baz': 23})
|
|
679
|
+
app = app.defaults(foo='bar', baz=23)
|
|
680
|
+
|
|
681
|
+
Default values for
|
|
682
|
+
:meth:`Pyjoyment.Controller.Pyjoyment_Controller.stash`, assigned for
|
|
683
|
+
every new request. ::
|
|
684
|
+
|
|
685
|
+
# Remove value
|
|
686
|
+
foo = app.defaults().pop('foo')
|
|
687
|
+
|
|
688
|
+
# Assign multiple values at once
|
|
689
|
+
app.defaults(foo='test', bar=23)
|
|
690
|
+
"""
|
|
691
|
+
return Pyjo.Util._stash('defaults', self, args, values) # pyright: ignore[reportPrivateUsage]
|
|
692
|
+
|
|
693
|
+
def delete(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
694
|
+
"""::
|
|
695
|
+
|
|
696
|
+
route = app.delete('/:foo', lambda c: ...)
|
|
697
|
+
|
|
698
|
+
Generate route with
|
|
699
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.delete` on
|
|
700
|
+
:attr:`routes`, takes the same arguments.
|
|
701
|
+
"""
|
|
702
|
+
return self.routes.delete(*args, **constraints)
|
|
703
|
+
|
|
704
|
+
def dispatch(self, c: Pyjoyment.Controller.object) -> None:
|
|
705
|
+
"""::
|
|
706
|
+
|
|
707
|
+
app.dispatch(Pyjoyment.Controller.new())
|
|
708
|
+
|
|
709
|
+
The heart of every :mod:`Pyjoyment` application, calls the
|
|
710
|
+
:attr:`static` and :attr:`routes` dispatchers for every request and
|
|
711
|
+
passes them a :mod:`Pyjoyment.Controller` object.
|
|
712
|
+
"""
|
|
713
|
+
plugins = self.plugins.emit_hook('before_dispatch', c)
|
|
714
|
+
|
|
715
|
+
stash = c.stash()
|
|
716
|
+
if stash.get('mojo.rendered'):
|
|
717
|
+
return
|
|
718
|
+
|
|
719
|
+
# Try to find a static file
|
|
720
|
+
tx: Any = c.tx
|
|
721
|
+
if not tx.res.code and self.static.dispatch(c):
|
|
722
|
+
plugins.emit_hook('after_static', c)
|
|
723
|
+
|
|
724
|
+
# Start timer (ignore static files)
|
|
725
|
+
if not stash.get('mojo.static'):
|
|
726
|
+
|
|
727
|
+
def message() -> str:
|
|
728
|
+
req = c.req
|
|
729
|
+
method = req.method
|
|
730
|
+
path = req.url.path.to_abs_str()
|
|
731
|
+
c.helpers.timing.begin('mojo.timer')
|
|
732
|
+
return f'{method} "{path}"'
|
|
733
|
+
|
|
734
|
+
c.helpers.log().trace(message)
|
|
735
|
+
|
|
736
|
+
# Routes
|
|
737
|
+
plugins.emit_hook('before_routes', c)
|
|
738
|
+
if not (tx.res.code or self.routes.dispatch(c) or tx.res.code):
|
|
739
|
+
c.helpers.reply.not_found()
|
|
740
|
+
|
|
741
|
+
def get(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
742
|
+
"""::
|
|
743
|
+
|
|
744
|
+
route = app.get('/:foo', lambda c: ...)
|
|
745
|
+
|
|
746
|
+
Generate route with
|
|
747
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.get` on
|
|
748
|
+
:attr:`routes`, takes the same arguments.
|
|
749
|
+
"""
|
|
750
|
+
return self.routes.get(*args, **constraints)
|
|
751
|
+
|
|
752
|
+
def handler(self, tx: Any) -> None:
|
|
753
|
+
"""::
|
|
754
|
+
|
|
755
|
+
app.handler(Pyjo.Transaction.HTTP.new())
|
|
756
|
+
app.handler(Pyjoyment.Controller.new())
|
|
757
|
+
|
|
758
|
+
Sets up the default controller and emits the ``around_dispatch`` hook
|
|
759
|
+
for every request.
|
|
760
|
+
"""
|
|
761
|
+
# Dispatcher has to be last in the chain
|
|
762
|
+
if not self._dispatch:
|
|
763
|
+
self._dispatch = True
|
|
764
|
+
self.hook('around_action', _action)
|
|
765
|
+
self.hook('around_dispatch', _dispatch)
|
|
766
|
+
|
|
767
|
+
# Process with chain
|
|
768
|
+
c = self.build_controller(tx)
|
|
769
|
+
self.plugins.emit_chain('around_dispatch', c)
|
|
770
|
+
|
|
771
|
+
# Delayed response
|
|
772
|
+
if not c.stash().get('mojo.rendered'):
|
|
773
|
+
c.helpers.log().trace('Nothing has been rendered, expecting delayed response (see FAQ for more)')
|
|
774
|
+
|
|
775
|
+
@overload
|
|
776
|
+
def helper[F: Callable[..., Any]](self, name: str, cb: None = None) -> Callable[[F], F]: ...
|
|
777
|
+
|
|
778
|
+
@overload
|
|
779
|
+
def helper[F: Callable[..., Any]](self, name: str, cb: F) -> F: ...
|
|
780
|
+
|
|
781
|
+
def helper(self, name: str, cb: Callable[..., Any] | None = None) -> Any:
|
|
782
|
+
"""::
|
|
783
|
+
|
|
784
|
+
app.helper('foo', lambda c, *args: ...)
|
|
785
|
+
|
|
786
|
+
@app.helper('foo')
|
|
787
|
+
def foo(c, *args): ...
|
|
788
|
+
|
|
789
|
+
Add or replace a helper that will be available as a method of the
|
|
790
|
+
controller object and the application object. For a full list of
|
|
791
|
+
helpers that are available by default see
|
|
792
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` and
|
|
793
|
+
:mod:`Pyjoyment.Plugin.TagHelpers`. ::
|
|
794
|
+
|
|
795
|
+
# Helper
|
|
796
|
+
cache = {}
|
|
797
|
+
app.helper('cache', lambda c: cache)
|
|
798
|
+
|
|
799
|
+
# Application
|
|
800
|
+
app.cache()['foo'] = 'bar'
|
|
801
|
+
result = app.cache()['foo']
|
|
802
|
+
|
|
803
|
+
# Controller
|
|
804
|
+
c.cache()['foo'] = 'bar'
|
|
805
|
+
result = c.cache()['foo']
|
|
806
|
+
"""
|
|
807
|
+
if cb is None:
|
|
808
|
+
|
|
809
|
+
def decorator(cb: Callable[..., Any]) -> Callable[..., Any]:
|
|
810
|
+
return self.helper(name, cb)
|
|
811
|
+
|
|
812
|
+
return decorator
|
|
813
|
+
self.renderer.add_helper(name, cb)
|
|
814
|
+
# Unlike Mojolicious: the helper is returned, the name of the decorated function, where Mojo returns the
|
|
815
|
+
# renderer, which its implementation happens to return
|
|
816
|
+
return cb
|
|
817
|
+
|
|
818
|
+
def hook(self, name: str, cb: Callable[..., Any] | None = None) -> Any:
|
|
819
|
+
"""::
|
|
820
|
+
|
|
821
|
+
app.hook('after_dispatch', lambda c: ...)
|
|
822
|
+
|
|
823
|
+
@app.hook('after_dispatch')
|
|
824
|
+
def after_dispatch(c): ...
|
|
825
|
+
|
|
826
|
+
Extend :mod:`Pyjoyment` with hooks, which allow code to be shared with
|
|
827
|
+
all requests indiscriminately, for a full list of available hooks see
|
|
828
|
+
the hooks above. ::
|
|
829
|
+
|
|
830
|
+
# Dispatchers will not run if there's already a response code defined
|
|
831
|
+
@app.hook('before_dispatch')
|
|
832
|
+
def before_dispatch(c):
|
|
833
|
+
if re.search(r'do_not_dispatch', c.req.url.path.to_route()):
|
|
834
|
+
c.render(text='Skipped static file server and router!')
|
|
835
|
+
"""
|
|
836
|
+
return self.plugins.on(name, cb)
|
|
837
|
+
|
|
838
|
+
def options(
|
|
839
|
+
self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint'
|
|
840
|
+
) -> Pyjoyment.Routes.Route.object:
|
|
841
|
+
"""::
|
|
842
|
+
|
|
843
|
+
route = app.options('/:foo', lambda c: ...)
|
|
844
|
+
|
|
845
|
+
Generate route with
|
|
846
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.options` on
|
|
847
|
+
:attr:`routes`, takes the same arguments.
|
|
848
|
+
"""
|
|
849
|
+
return self.routes.options(*args, **constraints)
|
|
850
|
+
|
|
851
|
+
def patch(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
852
|
+
"""::
|
|
853
|
+
|
|
854
|
+
route = app.patch('/:foo', lambda c: ...)
|
|
855
|
+
|
|
856
|
+
Generate route with
|
|
857
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.patch` on
|
|
858
|
+
:attr:`routes`, takes the same arguments.
|
|
859
|
+
"""
|
|
860
|
+
return self.routes.patch(*args, **constraints)
|
|
861
|
+
|
|
862
|
+
def plugin(self, name: str, conf: dict[str, Any] | None = None, /, **values: Any) -> Any:
|
|
863
|
+
"""::
|
|
864
|
+
|
|
865
|
+
app.plugin('some_thing')
|
|
866
|
+
app.plugin('some_thing', foo=23)
|
|
867
|
+
app.plugin('some_thing', {'foo': 23})
|
|
868
|
+
app.plugin('SomeThing')
|
|
869
|
+
app.plugin('SomeThing', foo=23)
|
|
870
|
+
app.plugin('SomeThing', {'foo': 23})
|
|
871
|
+
app.plugin('MyApp.Plugin.SomeThing')
|
|
872
|
+
app.plugin('MyApp.Plugin.SomeThing', foo=23)
|
|
873
|
+
app.plugin('MyApp.Plugin.SomeThing', {'foo': 23})
|
|
874
|
+
|
|
875
|
+
Load a plugin, for a full list of example plugins included in the
|
|
876
|
+
:mod:`Pyjoyment` distribution see :mod:`Pyjoyment.Plugins`.
|
|
877
|
+
"""
|
|
878
|
+
return self.plugins.register_plugin(name, self, conf, **values)
|
|
879
|
+
|
|
880
|
+
def post(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
881
|
+
"""::
|
|
882
|
+
|
|
883
|
+
route = app.post('/:foo', lambda c: ...)
|
|
884
|
+
|
|
885
|
+
Generate route with
|
|
886
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.post` on
|
|
887
|
+
:attr:`routes`, takes the same arguments.
|
|
888
|
+
"""
|
|
889
|
+
return self.routes.post(*args, **constraints)
|
|
890
|
+
|
|
891
|
+
def put(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
892
|
+
"""::
|
|
893
|
+
|
|
894
|
+
route = app.put('/:foo', lambda c: ...)
|
|
895
|
+
|
|
896
|
+
Generate route with
|
|
897
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.put` on
|
|
898
|
+
:attr:`routes`, takes the same arguments.
|
|
899
|
+
"""
|
|
900
|
+
return self.routes.put(*args, **constraints)
|
|
901
|
+
|
|
902
|
+
def query(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
903
|
+
"""::
|
|
904
|
+
|
|
905
|
+
route = app.query('/:foo', lambda c: ...)
|
|
906
|
+
|
|
907
|
+
Generate route with
|
|
908
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.query` on
|
|
909
|
+
:attr:`routes`, takes the same arguments.
|
|
910
|
+
"""
|
|
911
|
+
return self.routes.query(*args, **constraints)
|
|
912
|
+
|
|
913
|
+
def server(self, server: Pyjo.Server.object) -> None:
|
|
914
|
+
"""::
|
|
915
|
+
|
|
916
|
+
app.server(Pyjo.Server.new())
|
|
917
|
+
|
|
918
|
+
Emits the ``before_server_start`` hook.
|
|
919
|
+
"""
|
|
920
|
+
self.plugins.emit_hook('before_server_start', server, self)
|
|
921
|
+
|
|
922
|
+
# Not in Mojolicious: the end of the server, as server is its start
|
|
923
|
+
def server_stopped(self, server: Pyjo.Server.object) -> 'Pyjo.Promise.object | None':
|
|
924
|
+
"""::
|
|
925
|
+
|
|
926
|
+
promise = app.server_stopped(Pyjo.Server.new())
|
|
927
|
+
|
|
928
|
+
Emits the ``after_server_stop`` hook, and returns a promise of the
|
|
929
|
+
promises that its callbacks return.
|
|
930
|
+
"""
|
|
931
|
+
promises = [
|
|
932
|
+
promise
|
|
933
|
+
for cb in self.plugins.subscribers('after_server_stop')
|
|
934
|
+
if isinstance(promise := cb(server, self), Pyjo.Promise.object)
|
|
935
|
+
]
|
|
936
|
+
return Pyjo.Promise.all_settled(*promises) if promises else None
|
|
937
|
+
|
|
938
|
+
def start(self, *args: str) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
939
|
+
"""::
|
|
940
|
+
|
|
941
|
+
app.start()
|
|
942
|
+
app.start(*sys.argv[1:])
|
|
943
|
+
|
|
944
|
+
Start the command line interface for your application. For a full list
|
|
945
|
+
of commands that are available by default see
|
|
946
|
+
:mod:`Pyjoyment.Commands`. Note that the options ``-h``/``--help``,
|
|
947
|
+
``--home`` and ``-m``/``--mode``, which are shared by all commands,
|
|
948
|
+
will be parsed from ``sys.argv`` when :mod:`Pyjoyment.Commands` is
|
|
949
|
+
imported. ::
|
|
950
|
+
|
|
951
|
+
# Always start daemon
|
|
952
|
+
app.start('daemon', '-l', 'http://*:8080')
|
|
953
|
+
|
|
954
|
+
When the environment variable ``PYJO_APP_LOADER`` is set, as
|
|
955
|
+
:meth:`Pyjo.Server.Pyjo_Server.load_app` does, the application is
|
|
956
|
+
kept in :data:`Pyjo.Server.loaded_app` for the server instead.
|
|
957
|
+
"""
|
|
958
|
+
self.static.warmup()
|
|
959
|
+
self.renderer.warmup()
|
|
960
|
+
return self.commands.run(*(args or sys.argv[1:]))
|
|
961
|
+
|
|
962
|
+
def startup(self) -> None:
|
|
963
|
+
"""::
|
|
964
|
+
|
|
965
|
+
app.startup()
|
|
966
|
+
|
|
967
|
+
This is your main hook into the application, it will be called at
|
|
968
|
+
application startup. Meant to be overloaded in a subclass. ::
|
|
969
|
+
|
|
970
|
+
def startup(self): ...
|
|
971
|
+
"""
|
|
972
|
+
|
|
973
|
+
@overload
|
|
974
|
+
def template[F: Callable[..., Any]](self, name: str, cb: None = None) -> Callable[[F], F]: ...
|
|
975
|
+
|
|
976
|
+
@overload
|
|
977
|
+
def template[F: Callable[..., Any]](self, name: str, cb: F) -> F: ...
|
|
978
|
+
|
|
979
|
+
# Not in Mojolicious: the templates of functions, for the handler "py"
|
|
980
|
+
def template(self, name: str, cb: Callable[..., Any] | None = None) -> Any:
|
|
981
|
+
"""::
|
|
982
|
+
|
|
983
|
+
app.template('index.html', lambda c: t('<p>Hello!</p>'))
|
|
984
|
+
|
|
985
|
+
@app.template('index.html')
|
|
986
|
+
def index(c): ...
|
|
987
|
+
|
|
988
|
+
Add or replace a template of a function, which gets the controller
|
|
989
|
+
and the values of the stash that it names and returns a template
|
|
990
|
+
string, with
|
|
991
|
+
:meth:`Pyjoyment.Renderer.Pyjoyment_Renderer.add_template`, see
|
|
992
|
+
:mod:`Pyjoyment.Plugin.PyRenderer`. ::
|
|
993
|
+
|
|
994
|
+
# Template
|
|
995
|
+
@app.template('hello.html')
|
|
996
|
+
def hello(c, name):
|
|
997
|
+
return t('<p>Hello {name}!</p>')
|
|
998
|
+
|
|
999
|
+
# Controller
|
|
1000
|
+
c.render('hello', name='Joe')
|
|
1001
|
+
"""
|
|
1002
|
+
if cb is None:
|
|
1003
|
+
|
|
1004
|
+
def decorator(cb: Callable[..., Any]) -> Callable[..., Any]:
|
|
1005
|
+
return self.template(name, cb)
|
|
1006
|
+
|
|
1007
|
+
return decorator
|
|
1008
|
+
self.renderer.add_template(name, cb)
|
|
1009
|
+
return cb
|
|
1010
|
+
|
|
1011
|
+
def under(self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint') -> Pyjoyment.Routes.Route.object:
|
|
1012
|
+
"""::
|
|
1013
|
+
|
|
1014
|
+
route = app.under('/:foo', lambda c: ...)
|
|
1015
|
+
|
|
1016
|
+
Generate route with
|
|
1017
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.under` on
|
|
1018
|
+
:attr:`routes`, takes the same arguments.
|
|
1019
|
+
"""
|
|
1020
|
+
return self.routes.under(*args, **constraints)
|
|
1021
|
+
|
|
1022
|
+
def warmup(self) -> None:
|
|
1023
|
+
"""::
|
|
1024
|
+
|
|
1025
|
+
app.warmup()
|
|
1026
|
+
|
|
1027
|
+
Preload classes from :attr:`preload_namespaces` for future use.
|
|
1028
|
+
"""
|
|
1029
|
+
for namespace in self.preload_namespaces:
|
|
1030
|
+
Pyjo.Loader.load_classes(namespace)
|
|
1031
|
+
|
|
1032
|
+
def websocket(
|
|
1033
|
+
self, *args: Any, **constraints: 'Pyjoyment.Routes.Pattern.Constraint'
|
|
1034
|
+
) -> Pyjoyment.Routes.Route.object:
|
|
1035
|
+
"""::
|
|
1036
|
+
|
|
1037
|
+
route = app.websocket('/:foo', lambda c: ...)
|
|
1038
|
+
|
|
1039
|
+
Generate route with
|
|
1040
|
+
:meth:`Pyjoyment.Routes.Route.Pyjoyment_Routes_Route.websocket` on
|
|
1041
|
+
:attr:`routes`, takes the same arguments.
|
|
1042
|
+
"""
|
|
1043
|
+
return self.routes.websocket(*args, **constraints)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def _action(next: Any, c: Pyjoyment.Controller.object, action: Callable[..., Any], last: bool) -> Any:
|
|
1047
|
+
try:
|
|
1048
|
+
val = action(c)
|
|
1049
|
+
except TypeError as e:
|
|
1050
|
+
# Not in Mojolicious: the arguments of an action, which Perl does not check, as the placeholders of the route
|
|
1051
|
+
# as the arguments of another framework
|
|
1052
|
+
hint = 'an action gets the controller only, which has the values of the placeholders in c.param(...)'
|
|
1053
|
+
Pyjo.Util._call_hint(e, hint) # pyright: ignore[reportPrivateUsage]
|
|
1054
|
+
raise
|
|
1055
|
+
# Not in Mojolicious: the response that an endpoint returns, as a view of another framework does, which the
|
|
1056
|
+
# automatic rendering reports when it renders nothing
|
|
1057
|
+
if last and issubclass(type(val), (str, bytes, dict, list, tuple)):
|
|
1058
|
+
c.stash()['pyjo.returned'] = type(val).__name__
|
|
1059
|
+
# Not in Mojolicious: an action that is a coroutine function runs as a promise, which async sub of Mojo returns
|
|
1060
|
+
if inspect.iscoroutine(val):
|
|
1061
|
+
val = Pyjo.Promise.new(val)
|
|
1062
|
+
if isinstance(val, Pyjo.Promise.object):
|
|
1063
|
+
val.catch(functools.partial(_reply_exception, c))
|
|
1064
|
+
return val
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _dispatch(next: Callable[[], Any], c: Pyjoyment.Controller.object) -> None:
|
|
1068
|
+
c.app.dispatch(c)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _exception(next: Callable[[], Any], c: Pyjoyment.Controller.object) -> None:
|
|
1072
|
+
try:
|
|
1073
|
+
next()
|
|
1074
|
+
except Exception as e:
|
|
1075
|
+
c.helpers.reply.exception(e)
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def _reply_exception(c: Pyjoyment.Controller.object, e: Any) -> None:
|
|
1079
|
+
c.helpers.reply.exception(e)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
new = Pyjoyment_App.new
|
|
1083
|
+
object = Pyjoyment_App
|