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,1202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment.Plugin.DefaultHelpers - Default helpers plugin
|
|
3
|
+
========================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
# Pyjoyment
|
|
7
|
+
app.plugin('DefaultHelpers')
|
|
8
|
+
|
|
9
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` is a collection of helpers for
|
|
10
|
+
:mod:`Pyjoyment`.
|
|
11
|
+
|
|
12
|
+
This is a core plugin, that means it is always enabled and its code a good
|
|
13
|
+
example for learning to build new plugins, you're welcome to fork it.
|
|
14
|
+
|
|
15
|
+
See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
|
|
16
|
+
default.
|
|
17
|
+
|
|
18
|
+
Helpers
|
|
19
|
+
-------
|
|
20
|
+
|
|
21
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` implements the following helpers.
|
|
22
|
+
|
|
23
|
+
accepts ::
|
|
24
|
+
|
|
25
|
+
formats = c.accepts()
|
|
26
|
+
format = c.accepts('html', 'json', 'txt')
|
|
27
|
+
|
|
28
|
+
Select best possible representation for resource from ``format``
|
|
29
|
+
``GET``/``POST`` parameter, ``format`` stash value or ``Accept`` request header
|
|
30
|
+
with :meth:`Pyjoyment.Renderer.Pyjoyment_Renderer.accepts`, defaults to
|
|
31
|
+
returning the first extension if no preference could be detected. ::
|
|
32
|
+
|
|
33
|
+
# Check if JSON is acceptable
|
|
34
|
+
if c.accepts('json'):
|
|
35
|
+
c.render(json={'hello': 'world'})
|
|
36
|
+
|
|
37
|
+
# Check if JSON was specifically requested
|
|
38
|
+
if c.accepts('', 'json'):
|
|
39
|
+
c.render(json={'hello': 'world'})
|
|
40
|
+
|
|
41
|
+
# Unsupported representation
|
|
42
|
+
if not (format := c.accepts('html', 'json')):
|
|
43
|
+
c.render(data=b'', status=204)
|
|
44
|
+
|
|
45
|
+
# Detected representations to select from
|
|
46
|
+
formats = c.accepts()
|
|
47
|
+
|
|
48
|
+
app ::
|
|
49
|
+
|
|
50
|
+
%= app().secrets[0]
|
|
51
|
+
|
|
52
|
+
Alias for :attr:`Pyjoyment.Controller.Pyjoyment_Controller.app`.
|
|
53
|
+
|
|
54
|
+
b ::
|
|
55
|
+
|
|
56
|
+
%= b(b'Joel is a slug').slugify()
|
|
57
|
+
|
|
58
|
+
Turn bytes into a :mod:`Pyjo.String.Bytes` object.
|
|
59
|
+
|
|
60
|
+
c ::
|
|
61
|
+
|
|
62
|
+
%= c.c('a', 'b', 'c').shuffle().join()
|
|
63
|
+
|
|
64
|
+
Turn list into a :mod:`Pyjo.Collection` object. In templates, the variable
|
|
65
|
+
``c`` is the controller, which hides the helper.
|
|
66
|
+
|
|
67
|
+
config ::
|
|
68
|
+
|
|
69
|
+
%= config('something')
|
|
70
|
+
|
|
71
|
+
Alias for :meth:`Pyjo.App.Pyjo_App.config`.
|
|
72
|
+
|
|
73
|
+
content ::
|
|
74
|
+
|
|
75
|
+
%= content('foo', begin
|
|
76
|
+
test
|
|
77
|
+
% end)
|
|
78
|
+
%= content('bar', 'Hello World!')
|
|
79
|
+
%= content('foo')
|
|
80
|
+
%= content('bar')
|
|
81
|
+
%= content()
|
|
82
|
+
|
|
83
|
+
Store partial rendered content in a named buffer and retrieve it later,
|
|
84
|
+
defaults to retrieving the named buffer ``content``, which is used by the
|
|
85
|
+
renderer for the ``layout`` and ``extends`` features. New content will be
|
|
86
|
+
ignored if the named buffer is already in use.
|
|
87
|
+
|
|
88
|
+
content_for ::
|
|
89
|
+
|
|
90
|
+
% content_for('foo', begin
|
|
91
|
+
test
|
|
92
|
+
% end)
|
|
93
|
+
%= content_for('foo')
|
|
94
|
+
|
|
95
|
+
Same as ``content``, but appends content to named buffers if they are already
|
|
96
|
+
in use. ::
|
|
97
|
+
|
|
98
|
+
% content_for('message', begin
|
|
99
|
+
Hello
|
|
100
|
+
% end)
|
|
101
|
+
% content_for('message', begin
|
|
102
|
+
world!
|
|
103
|
+
% end)
|
|
104
|
+
%= content('message')
|
|
105
|
+
|
|
106
|
+
content_with ::
|
|
107
|
+
|
|
108
|
+
% content_with('foo', begin
|
|
109
|
+
test
|
|
110
|
+
% end)
|
|
111
|
+
%= content_with('foo')
|
|
112
|
+
|
|
113
|
+
Same as ``content``, but replaces content of named buffers if they are already
|
|
114
|
+
in use. ::
|
|
115
|
+
|
|
116
|
+
% content('message', begin
|
|
117
|
+
world!
|
|
118
|
+
% end)
|
|
119
|
+
% content_with('message', begin
|
|
120
|
+
Hello <%= content('message') %>
|
|
121
|
+
% end)
|
|
122
|
+
%= content('message')
|
|
123
|
+
|
|
124
|
+
csrf_token ::
|
|
125
|
+
|
|
126
|
+
%= csrf_token()
|
|
127
|
+
|
|
128
|
+
Get CSRF token from ``session``, and generate one if none exists. The token is
|
|
129
|
+
masked with a fresh random value on every call to protect against BREACH
|
|
130
|
+
attacks.
|
|
131
|
+
|
|
132
|
+
current_route ::
|
|
133
|
+
|
|
134
|
+
% if current_route('login'):
|
|
135
|
+
Welcome to Pyjoyment!
|
|
136
|
+
% end
|
|
137
|
+
%= current_route()
|
|
138
|
+
|
|
139
|
+
Check or get name of current route.
|
|
140
|
+
|
|
141
|
+
dumper ::
|
|
142
|
+
|
|
143
|
+
%= dumper({'some': 'data'})
|
|
144
|
+
|
|
145
|
+
Dump a Python data structure with :func:`Pyjo.Util.dumper`, very useful for
|
|
146
|
+
debugging.
|
|
147
|
+
|
|
148
|
+
exception_format ::
|
|
149
|
+
|
|
150
|
+
format = c.exception_format()
|
|
151
|
+
c = c.exception_format('txt')
|
|
152
|
+
|
|
153
|
+
Format for HTTP exceptions (``html``, ``json``, or ``txt``), defaults to the
|
|
154
|
+
value of :attr:`Pyjoyment.Pyjoyment_App.exception_format`.
|
|
155
|
+
|
|
156
|
+
extends ::
|
|
157
|
+
|
|
158
|
+
% extends('blue')
|
|
159
|
+
% extends('blue', title='Blue!')
|
|
160
|
+
|
|
161
|
+
Set ``extends`` stash value, all additional keyword arguments get merged into
|
|
162
|
+
the ``stash``.
|
|
163
|
+
|
|
164
|
+
flash ::
|
|
165
|
+
|
|
166
|
+
foo = c.flash('foo')
|
|
167
|
+
c = c.flash({'foo': 'bar'})
|
|
168
|
+
c = c.flash(foo='bar')
|
|
169
|
+
%= flash('foo')
|
|
170
|
+
|
|
171
|
+
Data storage persistent only for the next request, stored in the ``session``. ::
|
|
172
|
+
|
|
173
|
+
# Show message after redirect
|
|
174
|
+
c.flash(message='User created successfully!')
|
|
175
|
+
c.redirect_to('show_user', id=23)
|
|
176
|
+
|
|
177
|
+
inactivity_timeout ::
|
|
178
|
+
|
|
179
|
+
c = c.inactivity_timeout(3600)
|
|
180
|
+
|
|
181
|
+
Use :func:`Pyjo.IOLoop.stream` to find the current connection and increase
|
|
182
|
+
timeout if possible. ::
|
|
183
|
+
|
|
184
|
+
# Longer version
|
|
185
|
+
Pyjo.IOLoop.stream(c.tx.connection).timeout = 3600
|
|
186
|
+
|
|
187
|
+
include ::
|
|
188
|
+
|
|
189
|
+
%= include('menubar')
|
|
190
|
+
%= include('menubar', format='txt')
|
|
191
|
+
|
|
192
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.render_to_string`.
|
|
193
|
+
|
|
194
|
+
is_fresh ::
|
|
195
|
+
|
|
196
|
+
boolean = c.is_fresh()
|
|
197
|
+
boolean = c.is_fresh(etag='abc')
|
|
198
|
+
boolean = c.is_fresh(etag='W/"def"')
|
|
199
|
+
boolean = c.is_fresh(last_modified=epoch)
|
|
200
|
+
|
|
201
|
+
Check freshness of request by comparing the ``If-None-Match`` and
|
|
202
|
+
``If-Modified-Since`` request headers to the ``ETag`` and ``Last-Modified``
|
|
203
|
+
response headers with :meth:`Pyjoyment.Static.Pyjoyment_Static.is_fresh`. ::
|
|
204
|
+
|
|
205
|
+
# Add ETag/Last-Modified headers and check freshness before rendering
|
|
206
|
+
if c.is_fresh(etag='abc', last_modified=1424985708):
|
|
207
|
+
c.rendered(304)
|
|
208
|
+
else:
|
|
209
|
+
c.render(text='I ♥ Pyjoyment!')
|
|
210
|
+
|
|
211
|
+
layout ::
|
|
212
|
+
|
|
213
|
+
% layout('green')
|
|
214
|
+
% layout('green', title='Green!')
|
|
215
|
+
|
|
216
|
+
Set ``layout`` stash value, all additional keyword arguments get merged into
|
|
217
|
+
the ``stash``.
|
|
218
|
+
|
|
219
|
+
log ::
|
|
220
|
+
|
|
221
|
+
log = c.log()
|
|
222
|
+
|
|
223
|
+
Alternative to :attr:`Pyjoyment.Pyjoyment_App.log` that includes
|
|
224
|
+
:attr:`Pyjo.Message.Request.Pyjo_Message_Request.request_id` with every log
|
|
225
|
+
message, as the field ``request_id`` of a message of JSON, see
|
|
226
|
+
:class:`Pyjo.Log.Field`. ::
|
|
227
|
+
|
|
228
|
+
# Log message with context
|
|
229
|
+
c.log().debug('This is a log message with request id')
|
|
230
|
+
|
|
231
|
+
# Pass logger with context to model
|
|
232
|
+
log = c.log()
|
|
233
|
+
c.some_model().create({'foo': foo}, log)
|
|
234
|
+
|
|
235
|
+
param ::
|
|
236
|
+
|
|
237
|
+
%= param('foo')
|
|
238
|
+
|
|
239
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.param`.
|
|
240
|
+
|
|
241
|
+
proxy.get_p ::
|
|
242
|
+
|
|
243
|
+
promise = c.proxy.get_p('http://example.com', {'Accept': '*/*'})
|
|
244
|
+
|
|
245
|
+
Perform non-blocking ``GET`` request and forward response as efficiently as
|
|
246
|
+
possible, takes the same arguments as
|
|
247
|
+
:attr:`Pyjo.UserAgent.Pyjo_UserAgent.get` and returns a :mod:`Pyjo.Promise`
|
|
248
|
+
object. ::
|
|
249
|
+
|
|
250
|
+
# Forward with exception handling
|
|
251
|
+
@c.proxy.get_p('http://mojolicious.org').catch
|
|
252
|
+
def catch(err):
|
|
253
|
+
c.log().debug(f'Proxy error: {err}')
|
|
254
|
+
c.render(text='Something went wrong!', status=400)
|
|
255
|
+
|
|
256
|
+
proxy.post_p ::
|
|
257
|
+
|
|
258
|
+
promise = c.proxy.post_p('http://example.com', {'Accept': '*/*'})
|
|
259
|
+
|
|
260
|
+
Perform non-blocking ``POST`` request and forward response as efficiently as
|
|
261
|
+
possible, takes the same arguments as
|
|
262
|
+
:attr:`Pyjo.UserAgent.Pyjo_UserAgent.post` and returns a :mod:`Pyjo.Promise`
|
|
263
|
+
object. ::
|
|
264
|
+
|
|
265
|
+
# Forward with exception handling
|
|
266
|
+
@c.proxy.post_p('example.com', form={'test': 'pass'}).catch
|
|
267
|
+
def catch(err):
|
|
268
|
+
c.log().debug(f'Proxy error: {err}')
|
|
269
|
+
c.render(text='Something went wrong!', status=400)
|
|
270
|
+
|
|
271
|
+
proxy.start_p ::
|
|
272
|
+
|
|
273
|
+
promise = c.proxy.start_p(Pyjo.Transaction.HTTP.new())
|
|
274
|
+
|
|
275
|
+
Perform non-blocking request for a custom :mod:`Pyjo.Transaction.HTTP` object
|
|
276
|
+
and forward response as efficiently as possible, returns a :mod:`Pyjo.Promise`
|
|
277
|
+
object. ::
|
|
278
|
+
|
|
279
|
+
# Forward with exception handling
|
|
280
|
+
tx = c.ua().build_tx('GET', 'http://mojolicious.org')
|
|
281
|
+
|
|
282
|
+
@c.proxy.start_p(tx).catch
|
|
283
|
+
def catch(err):
|
|
284
|
+
c.log().debug(f'Proxy error: {err}')
|
|
285
|
+
c.render(text='Something went wrong!', status=400)
|
|
286
|
+
|
|
287
|
+
# Forward with custom request and response headers
|
|
288
|
+
headers = c.req.headers.clone().dehop()
|
|
289
|
+
headers.header('X-Proxy', 'Pyjo')
|
|
290
|
+
tx = c.ua().build_tx('GET', 'http://example.com', headers.to_dict())
|
|
291
|
+
c.proxy.start_p(tx)
|
|
292
|
+
tx.res.content.once('body', lambda content: c.res.headers.header('X-Proxy', 'Pyjo'))
|
|
293
|
+
|
|
294
|
+
redirect_to ::
|
|
295
|
+
|
|
296
|
+
c = c.redirect_to('named', foo='bar')
|
|
297
|
+
c = c.redirect_to('named', {'foo': 'bar'})
|
|
298
|
+
c = c.redirect_to('/index.html')
|
|
299
|
+
c = c.redirect_to('http://example.com/index.html')
|
|
300
|
+
|
|
301
|
+
Prepare a ``302`` (if the status code is not already ``3xx``) redirect response
|
|
302
|
+
with ``Location`` header, takes the same arguments as ``url_for``. ::
|
|
303
|
+
|
|
304
|
+
# Moved Permanently
|
|
305
|
+
c.res.code = 301
|
|
306
|
+
c.redirect_to('some_route')
|
|
307
|
+
|
|
308
|
+
# Temporary Redirect
|
|
309
|
+
c.res.code = 307
|
|
310
|
+
c.redirect_to('some_route')
|
|
311
|
+
|
|
312
|
+
reply.asset ::
|
|
313
|
+
|
|
314
|
+
c.reply.asset(Pyjo.Asset.File.new())
|
|
315
|
+
|
|
316
|
+
Reply with a :mod:`Pyjo.Asset.File` or :mod:`Pyjo.Asset.Memory` object using
|
|
317
|
+
:meth:`Pyjoyment.Static.Pyjoyment_Static.serve_asset`, and perform content
|
|
318
|
+
negotiation with ``Range``, ``If-Modified-Since`` and ``If-None-Match``
|
|
319
|
+
headers. ::
|
|
320
|
+
|
|
321
|
+
# Serve asset with custom modification time
|
|
322
|
+
asset = Pyjo.Asset.Memory.new()
|
|
323
|
+
asset.add_chunk(b'Hello World!').mtime = 784111777
|
|
324
|
+
c.res.headers.content_type = 'text/plain'
|
|
325
|
+
c.reply.asset(asset)
|
|
326
|
+
|
|
327
|
+
# Serve static file if it exists
|
|
328
|
+
if asset := c.app.static.file('images/logo.png'):
|
|
329
|
+
c.res.headers.content_type = 'image/png'
|
|
330
|
+
c.reply.asset(asset)
|
|
331
|
+
|
|
332
|
+
reply.exception ::
|
|
333
|
+
|
|
334
|
+
c = c.reply.exception('Oops!')
|
|
335
|
+
c = c.reply.exception(Pyjo.Exception.new())
|
|
336
|
+
|
|
337
|
+
Render an exception response in the appropriate format by delegating to more
|
|
338
|
+
specific exception helpers.
|
|
339
|
+
|
|
340
|
+
reply.file ::
|
|
341
|
+
|
|
342
|
+
c.reply.file('/etc/passwd')
|
|
343
|
+
|
|
344
|
+
Reply with a static file from an absolute path anywhere on the file system
|
|
345
|
+
using :attr:`Pyjoyment.Pyjoyment_App.static`. ::
|
|
346
|
+
|
|
347
|
+
# Longer version
|
|
348
|
+
c.reply.asset(Pyjo.Asset.File.new(path='/etc/passwd'))
|
|
349
|
+
|
|
350
|
+
# Serve file from an absolute path with a custom content type
|
|
351
|
+
c.res.headers.content_type = 'application/myapp'
|
|
352
|
+
c.reply.file('/home/sri/foo.txt')
|
|
353
|
+
|
|
354
|
+
# Serve file from a secret application directory
|
|
355
|
+
c.reply.file(c.app.home.child('secret', 'file.txt'))
|
|
356
|
+
|
|
357
|
+
reply.html_exception ::
|
|
358
|
+
|
|
359
|
+
c = c.reply.html_exception('Oops!')
|
|
360
|
+
c = c.reply.html_exception(Pyjo.Exception.new())
|
|
361
|
+
|
|
362
|
+
Render the exception template ``exception.$mode.$format.*`` or
|
|
363
|
+
``exception.$format.*`` and set the response status code to ``500``. Also sets
|
|
364
|
+
the stash values ``exception`` to a :mod:`Pyjo.Exception` object and
|
|
365
|
+
``snapshot`` to a copy of the ``stash`` for use in the templates.
|
|
366
|
+
|
|
367
|
+
reply.html_not_found ::
|
|
368
|
+
|
|
369
|
+
c = c.reply.html_not_found()
|
|
370
|
+
|
|
371
|
+
Render the not found template ``not_found.$mode.$format.*`` or
|
|
372
|
+
``not_found.$format.*`` and set the response status code to ``404``. Also sets
|
|
373
|
+
the stash value ``snapshot`` to a copy of the ``stash`` for use in the
|
|
374
|
+
templates.
|
|
375
|
+
|
|
376
|
+
reply.json_exception ::
|
|
377
|
+
|
|
378
|
+
c = c.reply.json_exception('Oops!')
|
|
379
|
+
c = c.reply.json_exception(Pyjo.Exception.new())
|
|
380
|
+
|
|
381
|
+
Render a JSON response and set the response status to ``500``.
|
|
382
|
+
|
|
383
|
+
reply.json_not_found ::
|
|
384
|
+
|
|
385
|
+
c = c.reply.json_not_found()
|
|
386
|
+
|
|
387
|
+
Render a JSON response and set the response status to ``404``.
|
|
388
|
+
|
|
389
|
+
reply.not_found ::
|
|
390
|
+
|
|
391
|
+
c = c.reply.not_found()
|
|
392
|
+
|
|
393
|
+
Render a not found response in the appropriate format by delegating to more
|
|
394
|
+
specific exception helpers.
|
|
395
|
+
|
|
396
|
+
reply.static ::
|
|
397
|
+
|
|
398
|
+
c.reply.static('images/logo.png')
|
|
399
|
+
c.reply.static('../lib/MyApp.py')
|
|
400
|
+
|
|
401
|
+
Reply with a static file using :attr:`Pyjoyment.Pyjoyment_App.static`, usually
|
|
402
|
+
from the ``public`` directories or ``DATA`` sections of your application. Note
|
|
403
|
+
that this helper uses a relative path, but does not protect from traversing to
|
|
404
|
+
parent directories. ::
|
|
405
|
+
|
|
406
|
+
# Serve file from a relative path with a custom content type
|
|
407
|
+
c.res.headers.content_type = 'application/myapp'
|
|
408
|
+
c.reply.static('foo.txt')
|
|
409
|
+
|
|
410
|
+
reply.txt_exception ::
|
|
411
|
+
|
|
412
|
+
c = c.reply.txt_exception('Oops!')
|
|
413
|
+
c = c.reply.txt_exception(Pyjo.Exception.new())
|
|
414
|
+
|
|
415
|
+
Render a plain text response and set the response status to ``500``.
|
|
416
|
+
|
|
417
|
+
reply.txt_not_found ::
|
|
418
|
+
|
|
419
|
+
c = c.reply.txt_not_found()
|
|
420
|
+
|
|
421
|
+
Render a plain text response and set the response status to ``404``.
|
|
422
|
+
|
|
423
|
+
respond_to ::
|
|
424
|
+
|
|
425
|
+
c = c.respond_to(json={'json': {'message': 'Welcome!'}}, html={'template': 'welcome'}, any=lambda c: ...)
|
|
426
|
+
|
|
427
|
+
Automatically select best possible representation for resource from ``format``
|
|
428
|
+
``GET``/``POST`` parameter, ``format`` stash value or ``Accept`` request
|
|
429
|
+
header, defaults to
|
|
430
|
+
:attr:`Pyjoyment.Renderer.Pyjoyment_Renderer.default_format` or rendering an
|
|
431
|
+
empty ``204`` response. Each representation can be handled with a callback or
|
|
432
|
+
a dict containing arguments to be passed to
|
|
433
|
+
:meth:`Pyjoyment.Controller.Pyjoyment_Controller.render`. ::
|
|
434
|
+
|
|
435
|
+
# Everything else than "json" and "xml" gets a 204 response
|
|
436
|
+
c.respond_to(
|
|
437
|
+
json=lambda c: c.render(json={'just': 'works'}),
|
|
438
|
+
xml={'text': '<just>works</just>'},
|
|
439
|
+
any={'data': b'', 'status': 204},
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
For more advanced negotiation logic you can also use ``accepts``.
|
|
443
|
+
|
|
444
|
+
session ::
|
|
445
|
+
|
|
446
|
+
%= session('foo')
|
|
447
|
+
|
|
448
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.session`.
|
|
449
|
+
|
|
450
|
+
stash ::
|
|
451
|
+
|
|
452
|
+
%= stash('foo')
|
|
453
|
+
% stash(foo='bar')
|
|
454
|
+
|
|
455
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.stash`. ::
|
|
456
|
+
|
|
457
|
+
%= stash('name') or 'Somebody'
|
|
458
|
+
|
|
459
|
+
timing.begin ::
|
|
460
|
+
|
|
461
|
+
c.timing.begin('foo')
|
|
462
|
+
|
|
463
|
+
Create named timestamp for ``timing.elapsed``.
|
|
464
|
+
|
|
465
|
+
timing.elapsed ::
|
|
466
|
+
|
|
467
|
+
elapsed = c.timing.elapsed('foo')
|
|
468
|
+
|
|
469
|
+
Return fractional amount of time in seconds since named timstamp has been
|
|
470
|
+
created with ``timing.begin`` or ``None`` if no such timestamp exists. ::
|
|
471
|
+
|
|
472
|
+
# Log timing information
|
|
473
|
+
c.timing.begin('database_stuff')
|
|
474
|
+
...
|
|
475
|
+
elapsed = c.timing.elapsed('database_stuff')
|
|
476
|
+
c.app.log.debug(f'Database stuff took {elapsed} seconds')
|
|
477
|
+
|
|
478
|
+
timing.rps ::
|
|
479
|
+
|
|
480
|
+
rps = c.timing.rps(0.001)
|
|
481
|
+
|
|
482
|
+
Return fractional number of requests that could be performed in one second if
|
|
483
|
+
every singe one took the given amount of time in seconds or ``None`` if the
|
|
484
|
+
number is too low. ::
|
|
485
|
+
|
|
486
|
+
# Log more timing information
|
|
487
|
+
c.timing.begin('web_stuff')
|
|
488
|
+
...
|
|
489
|
+
elapsed = c.timing.elapsed('web_stuff')
|
|
490
|
+
rps = c.timing.rps(elapsed)
|
|
491
|
+
c.app.log.debug(f'Web stuff took {elapsed} seconds ({rps} per second)')
|
|
492
|
+
|
|
493
|
+
timing.server_timing ::
|
|
494
|
+
|
|
495
|
+
c.timing.server_timing('metric')
|
|
496
|
+
c.timing.server_timing('metric', 'Some Description')
|
|
497
|
+
c.timing.server_timing('metric', 'Some Description', 0.001)
|
|
498
|
+
|
|
499
|
+
Create ``Server-Timing`` header with optional description and duration. ::
|
|
500
|
+
|
|
501
|
+
# "Server-Timing: miss"
|
|
502
|
+
c.timing.server_timing('miss')
|
|
503
|
+
|
|
504
|
+
# "Server-Timing: dc;desc=atl"
|
|
505
|
+
c.timing.server_timing('dc', 'atl')
|
|
506
|
+
|
|
507
|
+
# "Server-Timing: db;desc=Database;dur=0.0001"
|
|
508
|
+
c.timing.begin('database_stuff')
|
|
509
|
+
...
|
|
510
|
+
elapsed = c.timing.elapsed('database_stuff')
|
|
511
|
+
c.timing.server_timing('db', 'Database', elapsed)
|
|
512
|
+
|
|
513
|
+
# "Server-Timing: miss, dc;desc=atl"
|
|
514
|
+
c.timing.server_timing('miss')
|
|
515
|
+
c.timing.server_timing('dc', 'atl')
|
|
516
|
+
|
|
517
|
+
title ::
|
|
518
|
+
|
|
519
|
+
%= title()
|
|
520
|
+
% title('Welcome!')
|
|
521
|
+
% title('Welcome!', foo='bar')
|
|
522
|
+
|
|
523
|
+
Get or set ``title`` stash value, all additional keyword arguments get merged
|
|
524
|
+
into the ``stash``.
|
|
525
|
+
|
|
526
|
+
ua ::
|
|
527
|
+
|
|
528
|
+
%= ua().get('mojolicious.org').result.dom().at('title').text
|
|
529
|
+
|
|
530
|
+
Alias for :attr:`Pyjo.App.Pyjo_App.ua`.
|
|
531
|
+
|
|
532
|
+
url_for ::
|
|
533
|
+
|
|
534
|
+
%= url_for('named', foo='bar', baz='yada')
|
|
535
|
+
|
|
536
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for`. ::
|
|
537
|
+
|
|
538
|
+
%= url_for('/index.html').set(query={'foo': 'bar'})
|
|
539
|
+
|
|
540
|
+
url_for_asset ::
|
|
541
|
+
|
|
542
|
+
%= url_for_asset('/app.js')
|
|
543
|
+
|
|
544
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for_asset`.
|
|
545
|
+
|
|
546
|
+
url_for_file ::
|
|
547
|
+
|
|
548
|
+
%= url_for_file('/index.html')
|
|
549
|
+
|
|
550
|
+
Alias for :meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for_file`.
|
|
551
|
+
|
|
552
|
+
url_with ::
|
|
553
|
+
|
|
554
|
+
%= url_with('named', foo='bar', baz='yada')
|
|
555
|
+
|
|
556
|
+
Does the same as ``url_for``, but inherits query parameters from the current
|
|
557
|
+
request. ::
|
|
558
|
+
|
|
559
|
+
%= url_with().set(query={'page': 2})
|
|
560
|
+
|
|
561
|
+
validation ::
|
|
562
|
+
|
|
563
|
+
v = c.validation()
|
|
564
|
+
|
|
565
|
+
Get :mod:`Pyjoyment.Validator.Validation` object for current request to
|
|
566
|
+
validate file uploads as well as ``GET`` and ``POST`` parameters extracted
|
|
567
|
+
from the query string and ``application/x-www-form-urlencoded`` or
|
|
568
|
+
``multipart/form-data`` message body. Parts of the request body need to be
|
|
569
|
+
loaded into memory to parse ``POST`` parameters, so you have to make sure it
|
|
570
|
+
is not excessively large. There's a 16MiB limit for requests by default. ::
|
|
571
|
+
|
|
572
|
+
# Validate GET/POST parameter
|
|
573
|
+
v = c.validation()
|
|
574
|
+
v.required('title', 'trim').size(3, 50)
|
|
575
|
+
title = v.param('title')
|
|
576
|
+
|
|
577
|
+
# Validate file upload
|
|
578
|
+
v = c.validation()
|
|
579
|
+
v.required('tarball').upload().size(1, 1048576)
|
|
580
|
+
tarball = v.param('tarball')
|
|
581
|
+
|
|
582
|
+
See also
|
|
583
|
+
--------
|
|
584
|
+
|
|
585
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
586
|
+
|
|
587
|
+
Ported from `Mojolicious::Plugin::DefaultHelpers
|
|
588
|
+
<https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/DefaultHelpers.pm>`_ of Mojolicious 9.49.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
import Pyjo.Asset
|
|
592
|
+
import Pyjo.Asset.File
|
|
593
|
+
import Pyjo.Collection
|
|
594
|
+
import Pyjo.Exception
|
|
595
|
+
import Pyjo.IOLoop
|
|
596
|
+
import Pyjo.Log
|
|
597
|
+
import Pyjo.Promise
|
|
598
|
+
import Pyjo.String.Bytes
|
|
599
|
+
import Pyjo.String.HTML
|
|
600
|
+
import Pyjo.String.Unicode
|
|
601
|
+
import Pyjo.URL
|
|
602
|
+
import Pyjo.UserAgent
|
|
603
|
+
import Pyjoyment.Plugin
|
|
604
|
+
|
|
605
|
+
from Pyjo.Util import dumper, random_bytes
|
|
606
|
+
from Pyjoyment.Controller import Helper
|
|
607
|
+
|
|
608
|
+
import functools
|
|
609
|
+
import time
|
|
610
|
+
import weakref
|
|
611
|
+
|
|
612
|
+
from collections.abc import Callable
|
|
613
|
+
from typing import TYPE_CHECKING, Any
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
if TYPE_CHECKING:
|
|
617
|
+
import Pyjoyment
|
|
618
|
+
import Pyjoyment.Controller
|
|
619
|
+
import Pyjoyment.Validator.Validation
|
|
620
|
+
|
|
621
|
+
type Controller = Pyjoyment.Controller.object
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class Pyjoyment_Plugin_DefaultHelpers(Pyjoyment.Plugin.object):
|
|
625
|
+
"""
|
|
626
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` inherits all methods from
|
|
627
|
+
:mod:`Pyjoyment.Plugin` and implements the following new ones.
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
|
|
631
|
+
"""::
|
|
632
|
+
|
|
633
|
+
plugin.register(Pyjoyment.new())
|
|
634
|
+
|
|
635
|
+
Register helpers in :mod:`Pyjoyment` application.
|
|
636
|
+
"""
|
|
637
|
+
# Controller alias helpers
|
|
638
|
+
app.helper('app', _app)
|
|
639
|
+
for name in ('param', 'stash', 'session', 'url_for', 'url_for_asset', 'url_for_file'):
|
|
640
|
+
app.helper(name, _alias(name))
|
|
641
|
+
|
|
642
|
+
# Stash key shortcuts (should not generate log messages)
|
|
643
|
+
for name in ('extends', 'layout', 'title'):
|
|
644
|
+
app.helper(name, _stash_key(name))
|
|
645
|
+
|
|
646
|
+
app.helper('accepts', _accepts)
|
|
647
|
+
app.helper('b', _b)
|
|
648
|
+
# Not in Mojolicious: u, as Pyjo.String.Unicode is the Mojo::ByteStream of characters
|
|
649
|
+
app.helper('u', _u)
|
|
650
|
+
app.helper('c', _c)
|
|
651
|
+
app.helper('config', _config)
|
|
652
|
+
|
|
653
|
+
app.helper('content', functools.partial(_content, False, False))
|
|
654
|
+
app.helper('content_for', functools.partial(_content, True, False))
|
|
655
|
+
app.helper('content_with', functools.partial(_content, False, True))
|
|
656
|
+
|
|
657
|
+
app.helper('csrf_token', _csrf_token)
|
|
658
|
+
app.helper('current_route', _current_route)
|
|
659
|
+
app.helper('exception_format', _exception_format)
|
|
660
|
+
app.helper('flash', _flash)
|
|
661
|
+
app.helper('inactivity_timeout', _inactivity_timeout)
|
|
662
|
+
app.helper('is_fresh', _is_fresh)
|
|
663
|
+
app.helper('redirect_to', _redirect_to)
|
|
664
|
+
app.helper('respond_to', _respond_to)
|
|
665
|
+
app.helper('url_with', _url_with)
|
|
666
|
+
app.helper('validation', _validation)
|
|
667
|
+
|
|
668
|
+
app.helper('dumper', _dumper)
|
|
669
|
+
app.helper('include', _include)
|
|
670
|
+
|
|
671
|
+
app.helper('log', _log)
|
|
672
|
+
|
|
673
|
+
app.helper('proxy.get_p', functools.partial(_proxy_method_p, 'GET'))
|
|
674
|
+
app.helper('proxy.post_p', functools.partial(_proxy_method_p, 'POST'))
|
|
675
|
+
app.helper('proxy.start_p', _proxy_start_p)
|
|
676
|
+
|
|
677
|
+
app.helper('reply.asset', _asset)
|
|
678
|
+
app.helper('reply.file', _file)
|
|
679
|
+
app.helper('reply.static', _static)
|
|
680
|
+
|
|
681
|
+
app.helper('reply.exception', _exception)
|
|
682
|
+
app.helper('reply.not_found', _not_found)
|
|
683
|
+
app.helper('reply.http_exception', _http_exception)
|
|
684
|
+
app.helper('reply.http_not_found', _http_not_found)
|
|
685
|
+
app.helper('reply.html_exception', functools.partial(_development, 'exception'))
|
|
686
|
+
app.helper('reply.html_not_found', functools.partial(_development, 'not_found'))
|
|
687
|
+
app.helper('reply.json_exception', _json_exception)
|
|
688
|
+
app.helper('reply.json_not_found', _json_not_found)
|
|
689
|
+
app.helper('reply.txt_exception', _txt_exception)
|
|
690
|
+
app.helper('reply.txt_not_found', _txt_not_found)
|
|
691
|
+
|
|
692
|
+
app.helper('timing.begin', _timing_begin)
|
|
693
|
+
app.helper('timing.elapsed', _timing_elapsed)
|
|
694
|
+
app.helper('timing.rps', _timing_rps)
|
|
695
|
+
app.helper('timing.server_timing', _timing_server_timing)
|
|
696
|
+
|
|
697
|
+
app.helper('ua', _ua)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _accepts(c: 'Controller', *formats: str) -> list[str] | str | None:
|
|
701
|
+
return c.app.renderer.accepts(c, *formats)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _alias(name: str) -> Callable[..., Any]:
|
|
705
|
+
def helper(c: 'Controller', *args: Any, **values: Any) -> Any:
|
|
706
|
+
return getattr(c, name)(*args, **values)
|
|
707
|
+
|
|
708
|
+
return helper
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _app(c: 'Controller') -> 'Pyjoyment.object':
|
|
712
|
+
return c.app
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _asset(c: 'Controller', asset: Pyjo.Asset.object) -> 'Controller':
|
|
716
|
+
c.app.static.serve_asset(c, asset)
|
|
717
|
+
return c.rendered()
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _b(c: 'Controller', *args: Any) -> Pyjo.String.Bytes.object:
|
|
721
|
+
return Pyjo.String.Bytes.new(*args)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def _block(content: Any) -> Any:
|
|
725
|
+
return content() if callable(content) else content
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def _c(c: 'Controller', *args: Any) -> Pyjo.Collection.object[Any]:
|
|
729
|
+
return Pyjo.Collection.new(*args)
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def _config(c: 'Controller', *args: Any, **values: Any) -> Any:
|
|
733
|
+
return c.app.config(*args, **values)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def _content(append: bool, replace: bool, c: 'Controller', name: str | None = None, content: Any = None) -> Any:
|
|
737
|
+
name = name or 'content'
|
|
738
|
+
|
|
739
|
+
hash = c.stash().setdefault('mojo.content', {})
|
|
740
|
+
if content is not None:
|
|
741
|
+
if append:
|
|
742
|
+
# Joined as strings, as Mojo does: the concatenation of markup would escape a string that is not markup
|
|
743
|
+
hash[name] = str(hash.get(name) or '') + str(_block(content))
|
|
744
|
+
if replace:
|
|
745
|
+
hash[name] = _block(content)
|
|
746
|
+
elif hash.get(name) is None:
|
|
747
|
+
hash[name] = _block(content)
|
|
748
|
+
|
|
749
|
+
content = hash.get(name)
|
|
750
|
+
return Pyjo.String.HTML.new(content if content is not None else '')
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def _convert_to_exception(e: Any) -> Pyjo.Exception.object:
|
|
754
|
+
return e if isinstance(e, Pyjo.Exception.object) else Pyjo.Exception.new(e)
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def _csrf_token(c: 'Controller') -> str:
|
|
758
|
+
# Generate token and store it in the session
|
|
759
|
+
session = c.session()
|
|
760
|
+
if not session.get('csrf_token'):
|
|
761
|
+
session['csrf_token'] = random_bytes(20).hex()
|
|
762
|
+
|
|
763
|
+
# Mask token to protect against BREACH attacks
|
|
764
|
+
mask = random_bytes(20)
|
|
765
|
+
return mask.hex() + bytes(a ^ b for a, b in zip(mask, bytes.fromhex(session['csrf_token']), strict=False)).hex()
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _current_route(c: 'Controller', *args: str) -> str | bool | None:
|
|
769
|
+
if (route := c.match.endpoint) is None:
|
|
770
|
+
return ''
|
|
771
|
+
return route.name == args[0] if args else route.name
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def _development(page: str, c: 'Controller', e: Any = None) -> 'Controller':
|
|
775
|
+
if page == 'exception':
|
|
776
|
+
c.helpers.log().error((e := _convert_to_exception(e)).inspect())
|
|
777
|
+
|
|
778
|
+
# Filtered stash snapshot
|
|
779
|
+
stash = c.stash()
|
|
780
|
+
stash['snapshot'] = {
|
|
781
|
+
key: 'DUMMY' if key == 'app' else value
|
|
782
|
+
for key, value in stash.items()
|
|
783
|
+
if not key.startswith('mojo.') and value is not None
|
|
784
|
+
}
|
|
785
|
+
stash['exception'] = e if page == 'exception' else None
|
|
786
|
+
|
|
787
|
+
# Render with fallbacks
|
|
788
|
+
app = c.app
|
|
789
|
+
mode = app.mode
|
|
790
|
+
options = {
|
|
791
|
+
'format': stash.get('format') or app.renderer.default_format,
|
|
792
|
+
'handler': None,
|
|
793
|
+
'status': 500 if page == 'exception' else 404,
|
|
794
|
+
'template': f'{page}.{mode}',
|
|
795
|
+
}
|
|
796
|
+
# Renamed from Mojolicious: the bundled templates are in pyjo/, where Mojo has them in mojo/
|
|
797
|
+
bundled = 'pyjo/' + ('debug' if mode == 'development' else page)
|
|
798
|
+
if _fallbacks(c, options, page, bundled):
|
|
799
|
+
return c
|
|
800
|
+
_fallbacks(c, {**options, 'format': 'html'}, page, bundled)
|
|
801
|
+
return c
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def _dumper(c: 'Controller', *values: Any) -> str:
|
|
805
|
+
return dumper(*values)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def _exception(c: 'Controller', e: Any = None) -> Any:
|
|
809
|
+
return c.helpers.reply.http_exception(e)
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def _exception_format(c: 'Controller', *args: str) -> Any:
|
|
813
|
+
stash = c.stash()
|
|
814
|
+
if not stash.get('mojo.exception_format'):
|
|
815
|
+
stash['mojo.exception_format'] = c.app.exception_format
|
|
816
|
+
if not args:
|
|
817
|
+
return stash['mojo.exception_format']
|
|
818
|
+
stash['mojo.exception_format'] = args[0]
|
|
819
|
+
return c
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def _fallbacks(c: 'Controller', options: dict[str, Any], template: str, bundled: str) -> bool:
|
|
823
|
+
# Mode specific template
|
|
824
|
+
if c.render_maybe(**options):
|
|
825
|
+
return True
|
|
826
|
+
|
|
827
|
+
# Normal template
|
|
828
|
+
if c.render_maybe(**{**options, 'template': template}):
|
|
829
|
+
return True
|
|
830
|
+
|
|
831
|
+
# Inline template
|
|
832
|
+
stash = c.stash()
|
|
833
|
+
if options['format'] != 'html':
|
|
834
|
+
return False
|
|
835
|
+
stash.pop('extends', None)
|
|
836
|
+
stash.pop('layout', None)
|
|
837
|
+
return bool(c.render_maybe(bundled, **{**options, 'handler': 'ep'}))
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def _file(c: 'Controller', path: Any) -> 'Controller':
|
|
841
|
+
return _asset(c, Pyjo.Asset.File.new(path=str(path)))
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def _flash(c: 'Controller', *args: Any, **values: Any) -> Any:
|
|
845
|
+
# Check old flash
|
|
846
|
+
session = c.session()
|
|
847
|
+
if len(args) == 1 and not isinstance(args[0], dict) and not values:
|
|
848
|
+
return flash.get(args[0]) if (flash := session.get('flash')) is not None else None
|
|
849
|
+
|
|
850
|
+
# Initialize new flash and merge values
|
|
851
|
+
new_flash = session.setdefault('new_flash', {})
|
|
852
|
+
for arg in args:
|
|
853
|
+
new_flash.update(arg)
|
|
854
|
+
new_flash.update(values)
|
|
855
|
+
|
|
856
|
+
return c
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _http_exception(c: 'Controller', e: Any) -> Any:
|
|
860
|
+
format = c.exception_format()
|
|
861
|
+
if format == 'txt':
|
|
862
|
+
return c.helpers.reply.txt_exception(e)
|
|
863
|
+
if format == 'json':
|
|
864
|
+
return c.helpers.reply.json_exception(e)
|
|
865
|
+
return c.helpers.reply.html_exception(e)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _http_not_found(c: 'Controller') -> Any:
|
|
869
|
+
format = c.exception_format()
|
|
870
|
+
if format == 'txt':
|
|
871
|
+
return c.helpers.reply.txt_not_found()
|
|
872
|
+
if format == 'json':
|
|
873
|
+
return c.helpers.reply.json_not_found()
|
|
874
|
+
return c.helpers.reply.html_not_found()
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def _inactivity_timeout(c: 'Controller', timeout: float) -> 'Controller':
|
|
878
|
+
tx: Any = c.tx
|
|
879
|
+
if stream := Pyjo.IOLoop.stream(tx.connection or ''):
|
|
880
|
+
stream.timeout = timeout
|
|
881
|
+
return c
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def _include(c: 'Controller', *args: Any, **values: Any) -> Pyjo.String.HTML.object | Pyjo.String.Bytes.object | None:
|
|
885
|
+
return c.render_to_string(*args, **values)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def _is_fresh(c: 'Controller', *, etag: str | None = None, last_modified: float | None = None) -> bool:
|
|
889
|
+
return c.app.static.is_fresh(c, etag=etag, last_modified=last_modified)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def _json_exception(c: 'Controller', e: Any) -> Any:
|
|
893
|
+
c.stash()['exception'] = _convert_to_exception(e)
|
|
894
|
+
if c.app.mode == 'development':
|
|
895
|
+
return c.render(json={'error': e}, status=500)
|
|
896
|
+
return c.render(json={'error': 'Internal Server Error'}, status=500)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _json_not_found(c: 'Controller') -> Any:
|
|
900
|
+
return c.render(json={'error': 'Not Found'}, status=404)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def _log(c: 'Controller') -> Pyjo.Log.object:
|
|
904
|
+
stash = c.stash()
|
|
905
|
+
if not stash.get('mojo.log'):
|
|
906
|
+
# Not in Mojolicious: the request id as a field of its own of a message of JSON
|
|
907
|
+
id = c.req.request_id
|
|
908
|
+
stash['mojo.log'] = c.app.log.context(Pyjo.Log.Field('request_id', id, f'[{id}]'))
|
|
909
|
+
return stash['mojo.log']
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def _not_found(c: 'Controller') -> Any:
|
|
913
|
+
return c.helpers.reply.http_not_found()
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def _proxy_method_p(method: str, c: 'Controller', *args: Any, **kwargs: Any) -> Pyjo.Promise.object:
|
|
917
|
+
return _proxy_start_p(c, c.ua().build_tx(method, *args, **kwargs))
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def _proxy_start_p(c: 'Controller', source_tx: Any) -> Pyjo.Promise.object:
|
|
921
|
+
tx: Any = c.render_later().tx
|
|
922
|
+
|
|
923
|
+
promise = Pyjo.Promise.new()
|
|
924
|
+
source = weakref.ref(source_tx)
|
|
925
|
+
|
|
926
|
+
def body(source_content: Any) -> None:
|
|
927
|
+
source_tx = source()
|
|
928
|
+
if source_tx is None:
|
|
929
|
+
return
|
|
930
|
+
|
|
931
|
+
source_res = source_tx.res
|
|
932
|
+
res = tx.res
|
|
933
|
+
content = res.content
|
|
934
|
+
res.code = source_res.code
|
|
935
|
+
res.message = source_res.message
|
|
936
|
+
headers = source_res.headers.clone().dehop()
|
|
937
|
+
content.headers = headers
|
|
938
|
+
promise.resolve()
|
|
939
|
+
|
|
940
|
+
source_stream = Pyjo.IOLoop.stream(source_tx.connection)
|
|
941
|
+
if not (stream := Pyjo.IOLoop.stream(tx.connection)):
|
|
942
|
+
return
|
|
943
|
+
|
|
944
|
+
write = content.write_chunk if source_content.is_chunked else content.write
|
|
945
|
+
|
|
946
|
+
def read(source_content: Any, data: bytes) -> None:
|
|
947
|
+
write(data if len(data) else None)
|
|
948
|
+
tx.resume()
|
|
949
|
+
|
|
950
|
+
# Throttle transparently when backpressure rises
|
|
951
|
+
if stream.can_write:
|
|
952
|
+
return
|
|
953
|
+
source_stream.stop()
|
|
954
|
+
|
|
955
|
+
@stream.once('drain')
|
|
956
|
+
def drain(stream: Any) -> None:
|
|
957
|
+
source_stream.start()
|
|
958
|
+
|
|
959
|
+
source_content.unsubscribe('read').on('read', read)
|
|
960
|
+
|
|
961
|
+
def finish(source_res: Any) -> None:
|
|
962
|
+
write(b'')
|
|
963
|
+
tx.resume()
|
|
964
|
+
|
|
965
|
+
source_res.once('finish', finish)
|
|
966
|
+
|
|
967
|
+
source_tx.res.content.set(auto_upgrade=False, auto_decompress=False).once('body', body)
|
|
968
|
+
|
|
969
|
+
@source_tx.once('finish')
|
|
970
|
+
def finish(source_tx: Any) -> None:
|
|
971
|
+
promise.reject(_tx_error(source_tx))
|
|
972
|
+
|
|
973
|
+
def ignore(*args: Any) -> None:
|
|
974
|
+
pass
|
|
975
|
+
|
|
976
|
+
c.ua().start_p(source_tx).catch(ignore)
|
|
977
|
+
|
|
978
|
+
return promise
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def _redirect_to(c: 'Controller', *args: Any, **values: Any) -> 'Controller':
|
|
982
|
+
# Don't override 3xx status
|
|
983
|
+
res = c.res
|
|
984
|
+
res.headers.location = c.url_for(*args, **values).to_str()
|
|
985
|
+
return c.rendered(None if res.is_redirect else 302)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def _respond_to(c: 'Controller', *args: dict[str, Any], **values: Any) -> 'Controller':
|
|
989
|
+
targets = args[0] if args else values
|
|
990
|
+
|
|
991
|
+
# Find target
|
|
992
|
+
target = None
|
|
993
|
+
renderer = c.app.renderer
|
|
994
|
+
formats: list[str] = renderer.accepts(c) or [] # pyright: ignore[reportAssignmentType]
|
|
995
|
+
for format in formats or [renderer.default_format]:
|
|
996
|
+
if not (target := targets.get(format)):
|
|
997
|
+
continue
|
|
998
|
+
c.stash()['format'] = format
|
|
999
|
+
break
|
|
1000
|
+
|
|
1001
|
+
# Fallback
|
|
1002
|
+
if not target:
|
|
1003
|
+
if not (target := targets.get('any')):
|
|
1004
|
+
return c.rendered(204)
|
|
1005
|
+
c.stash().pop('format', None)
|
|
1006
|
+
|
|
1007
|
+
# Dispatch
|
|
1008
|
+
if callable(target):
|
|
1009
|
+
target(c)
|
|
1010
|
+
else:
|
|
1011
|
+
c.render(**target)
|
|
1012
|
+
|
|
1013
|
+
return c
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def _timing_begin(c: 'Controller', name: str) -> None:
|
|
1017
|
+
c.stash().setdefault('mojo.timing', {})[name] = time.time()
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def _timing_elapsed(c: 'Controller', name: str) -> float | None:
|
|
1021
|
+
if (started := c.stash().get('mojo.timing', {}).get(name)) is None:
|
|
1022
|
+
return None
|
|
1023
|
+
return time.time() - started
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _timing_rps(c: 'Controller', elapsed: float | str) -> str | None:
|
|
1027
|
+
return None if float(elapsed) == 0 else f'{1 / float(elapsed):.3f}'
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def _timing_server_timing(c: 'Controller', metric: str, desc: str | None = None, dur: Any = None) -> None:
|
|
1031
|
+
value = metric
|
|
1032
|
+
if desc is not None:
|
|
1033
|
+
value += f';desc="{desc}"'
|
|
1034
|
+
if dur is not None:
|
|
1035
|
+
value += f';dur={dur}'
|
|
1036
|
+
c.res.headers.append('Server-Timing', value)
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def _tx_error(tx: Any) -> str:
|
|
1040
|
+
error: dict[str, Any] = tx.error or {}
|
|
1041
|
+
return str(error.get('message') or 'Unknown error')
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def _txt_exception(c: 'Controller', e: Any) -> Any:
|
|
1045
|
+
c.stash()['exception'] = _convert_to_exception(e)
|
|
1046
|
+
if c.app.mode == 'development':
|
|
1047
|
+
return c.render(text=str(e), format='txt', status=500)
|
|
1048
|
+
return c.render(text='Internal Server Error', format='txt', status=500)
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def _txt_not_found(c: 'Controller') -> Any:
|
|
1052
|
+
return c.render(text='Not Found', format='txt', status=404)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def _static(c: 'Controller', file: str) -> 'Controller':
|
|
1056
|
+
if not c.app.static.serve(c, file):
|
|
1057
|
+
raise Pyjo.Exception.new(f'Static file "{file}" not found')
|
|
1058
|
+
return c.rendered()
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def _stash_key(name: str) -> Callable[..., Any]:
|
|
1062
|
+
def helper(c: 'Controller', *args: Any, **values: Any) -> Any:
|
|
1063
|
+
if not args and not values:
|
|
1064
|
+
return c.stash(name)
|
|
1065
|
+
return c.stash({name: args[0]} if args else {}, **values)
|
|
1066
|
+
|
|
1067
|
+
return helper
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def _url_with(c: 'Controller', *args: Any, **values: Any) -> Pyjo.URL.object:
|
|
1071
|
+
url = c.url_for(*args, **values)
|
|
1072
|
+
url.query = c.req.url.query.clone()
|
|
1073
|
+
return url
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
def _u(c: 'Controller', *args: Any) -> Pyjo.String.Unicode.object:
|
|
1077
|
+
return Pyjo.String.Unicode.new(*args)
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def _ua(c: 'Controller') -> Pyjo.UserAgent.object:
|
|
1081
|
+
return c.app.ua
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def _validation(c: 'Controller') -> 'Pyjoyment.Validator.Validation.object':
|
|
1085
|
+
stash = c.stash()
|
|
1086
|
+
if (v := stash.get('mojo.validation')) is not None:
|
|
1087
|
+
return v
|
|
1088
|
+
|
|
1089
|
+
req = c.req
|
|
1090
|
+
token = c.session().get('csrf_token')
|
|
1091
|
+
header = req.headers.header('X-CSRF-Token')
|
|
1092
|
+
hash: dict[str, Any] = dict(req.params.to_dict())
|
|
1093
|
+
if token and header and hash.get('csrf_token') is None:
|
|
1094
|
+
hash['csrf_token'] = header
|
|
1095
|
+
for name in [upload.name for upload in req.uploads()]:
|
|
1096
|
+
if name is not None:
|
|
1097
|
+
hash[name] = req.every_upload(name)
|
|
1098
|
+
v = c.app.validator.validation().set(input=hash, csrf_token=token)
|
|
1099
|
+
stash['mojo.validation'] = v
|
|
1100
|
+
return v
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
# Not in Mojolicious: the types of the helpers, for type checkers, which Pyjoyment.Controller and Pyjoyment have
|
|
1104
|
+
class ProxyHelpers:
|
|
1105
|
+
"""
|
|
1106
|
+
The types of the nested helpers ``proxy.*``, for type checkers.
|
|
1107
|
+
"""
|
|
1108
|
+
|
|
1109
|
+
get_p = Helper(functools.partial(_proxy_method_p, 'GET'))
|
|
1110
|
+
post_p = Helper(functools.partial(_proxy_method_p, 'POST'))
|
|
1111
|
+
start_p = Helper(_proxy_start_p)
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
class ReplyHelpers:
|
|
1115
|
+
"""
|
|
1116
|
+
The types of the nested helpers ``reply.*``, for type checkers.
|
|
1117
|
+
"""
|
|
1118
|
+
|
|
1119
|
+
asset = Helper(_asset)
|
|
1120
|
+
file = Helper(_file)
|
|
1121
|
+
static = Helper(_static)
|
|
1122
|
+
|
|
1123
|
+
exception = Helper(_exception)
|
|
1124
|
+
not_found = Helper(_not_found)
|
|
1125
|
+
http_exception = Helper(_http_exception)
|
|
1126
|
+
http_not_found = Helper(_http_not_found)
|
|
1127
|
+
html_exception = Helper(functools.partial(_development, 'exception'))
|
|
1128
|
+
html_not_found = Helper(functools.partial(_development, 'not_found'))
|
|
1129
|
+
json_exception = Helper(_json_exception)
|
|
1130
|
+
json_not_found = Helper(_json_not_found)
|
|
1131
|
+
txt_exception = Helper(_txt_exception)
|
|
1132
|
+
txt_not_found = Helper(_txt_not_found)
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
class TimingHelpers:
|
|
1136
|
+
"""
|
|
1137
|
+
The types of the nested helpers ``timing.*``, for type checkers.
|
|
1138
|
+
"""
|
|
1139
|
+
|
|
1140
|
+
begin = Helper(_timing_begin)
|
|
1141
|
+
elapsed = Helper(_timing_elapsed)
|
|
1142
|
+
rps = Helper(_timing_rps)
|
|
1143
|
+
server_timing = Helper(_timing_server_timing)
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
class Helpers:
|
|
1147
|
+
"""
|
|
1148
|
+
The types of the helpers of :mod:`Pyjoyment.Plugin.DefaultHelpers`, for
|
|
1149
|
+
type checkers and editors, which :mod:`Pyjoyment.Controller` and
|
|
1150
|
+
:mod:`Pyjoyment` have, while the helpers themselves are found in the
|
|
1151
|
+
renderer of the application at run time. See
|
|
1152
|
+
:class:`Pyjoyment.Controller.Helper`.
|
|
1153
|
+
"""
|
|
1154
|
+
|
|
1155
|
+
app = Helper(_app)
|
|
1156
|
+
param = Helper(_alias('param'))
|
|
1157
|
+
stash = Helper(_alias('stash'))
|
|
1158
|
+
session = Helper(_alias('session'))
|
|
1159
|
+
url_for = Helper(_alias('url_for'))
|
|
1160
|
+
url_for_asset = Helper(_alias('url_for_asset'))
|
|
1161
|
+
url_for_file = Helper(_alias('url_for_file'))
|
|
1162
|
+
|
|
1163
|
+
extends = Helper(_stash_key('extends'))
|
|
1164
|
+
layout = Helper(_stash_key('layout'))
|
|
1165
|
+
title = Helper(_stash_key('title'))
|
|
1166
|
+
|
|
1167
|
+
accepts = Helper(_accepts)
|
|
1168
|
+
b = Helper(_b)
|
|
1169
|
+
u = Helper(_u)
|
|
1170
|
+
c = Helper(_c)
|
|
1171
|
+
config = Helper(_config)
|
|
1172
|
+
|
|
1173
|
+
content = Helper(functools.partial(_content, False, False))
|
|
1174
|
+
content_for = Helper(functools.partial(_content, True, False))
|
|
1175
|
+
content_with = Helper(functools.partial(_content, False, True))
|
|
1176
|
+
|
|
1177
|
+
csrf_token = Helper(_csrf_token)
|
|
1178
|
+
current_route = Helper(_current_route)
|
|
1179
|
+
exception_format = Helper(_exception_format)
|
|
1180
|
+
flash = Helper(_flash)
|
|
1181
|
+
inactivity_timeout = Helper(_inactivity_timeout)
|
|
1182
|
+
is_fresh = Helper(_is_fresh)
|
|
1183
|
+
redirect_to = Helper(_redirect_to)
|
|
1184
|
+
respond_to = Helper(_respond_to)
|
|
1185
|
+
url_with = Helper(_url_with)
|
|
1186
|
+
validation = Helper(_validation)
|
|
1187
|
+
|
|
1188
|
+
dumper = Helper(_dumper)
|
|
1189
|
+
include = Helper(_include)
|
|
1190
|
+
|
|
1191
|
+
log = Helper(_log)
|
|
1192
|
+
|
|
1193
|
+
proxy: ProxyHelpers
|
|
1194
|
+
reply: ReplyHelpers
|
|
1195
|
+
timing: TimingHelpers
|
|
1196
|
+
|
|
1197
|
+
ua = Helper(_ua)
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
new = Pyjoyment_Plugin_DefaultHelpers.new
|
|
1201
|
+
object = Pyjoyment_Plugin_DefaultHelpers
|
|
1202
|
+
DefaultHelpers = Pyjoyment_Plugin_DefaultHelpers
|