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.
Files changed (200) hide show
  1. Pyjo/App/ASGI.py +735 -0
  2. Pyjo/App/CGI.py +773 -0
  3. Pyjo/App/FastCGI.py +631 -0
  4. Pyjo/App/Lambda.py +987 -0
  5. Pyjo/App/WSGI.py +427 -0
  6. Pyjo/App/__init__.py +409 -0
  7. Pyjo/Asset/File.py +377 -0
  8. Pyjo/Asset/Memory.py +235 -0
  9. Pyjo/Asset/__init__.py +200 -0
  10. Pyjo/Base.py +215 -0
  11. Pyjo/Cache.py +119 -0
  12. Pyjo/Collection.py +570 -0
  13. Pyjo/Content/MultiPart.py +325 -0
  14. Pyjo/Content/Single.py +193 -0
  15. Pyjo/Content/__init__.py +806 -0
  16. Pyjo/Cookie/Request.py +90 -0
  17. Pyjo/Cookie/Response.py +294 -0
  18. Pyjo/Cookie/__init__.py +143 -0
  19. Pyjo/DOM/CSS.py +1019 -0
  20. Pyjo/DOM/HTML.py +550 -0
  21. Pyjo/DOM/__init__.py +1329 -0
  22. Pyjo/Date.py +294 -0
  23. Pyjo/EventEmitter.py +522 -0
  24. Pyjo/Exception.py +494 -0
  25. Pyjo/File.py +959 -0
  26. Pyjo/GetOpt.py +605 -0
  27. Pyjo/Headers.py +760 -0
  28. Pyjo/HelloWorld.py +77 -0
  29. Pyjo/Home.py +109 -0
  30. Pyjo/IOLoop/ChildProcess.py +591 -0
  31. Pyjo/IOLoop/Client.py +736 -0
  32. Pyjo/IOLoop/Datagram.py +982 -0
  33. Pyjo/IOLoop/Messages.py +664 -0
  34. Pyjo/IOLoop/Server.py +458 -0
  35. Pyjo/IOLoop/Stdio.py +446 -0
  36. Pyjo/IOLoop/Stream.py +644 -0
  37. Pyjo/IOLoop/Subprocess.py +419 -0
  38. Pyjo/IOLoop/TLS.py +360 -0
  39. Pyjo/IOLoop/__init__.py +1036 -0
  40. Pyjo/IOLoop/certs/README.md +41 -0
  41. Pyjo/IOLoop/certs/ca.crt +20 -0
  42. Pyjo/IOLoop/certs/server.crt +21 -0
  43. Pyjo/IOLoop/certs/server.key +28 -0
  44. Pyjo/JSON/Pointer.py +135 -0
  45. Pyjo/JSON/RPC.py +772 -0
  46. Pyjo/JSON/Schema.py +2506 -0
  47. Pyjo/JSON/__init__.py +573 -0
  48. Pyjo/Loader.py +342 -0
  49. Pyjo/Log.py +723 -0
  50. Pyjo/Message/Request.py +725 -0
  51. Pyjo/Message/Response.py +476 -0
  52. Pyjo/Message/__init__.py +830 -0
  53. Pyjo/Parameters.py +472 -0
  54. Pyjo/Path.py +419 -0
  55. Pyjo/Promise.py +894 -0
  56. Pyjo/Prove.py +1305 -0
  57. Pyjo/Reactor/Asyncio.py +854 -0
  58. Pyjo/Reactor/Poll.py +439 -0
  59. Pyjo/Reactor/__init__.py +525 -0
  60. Pyjo/SSE.py +119 -0
  61. Pyjo/Server/ASGI.py +486 -0
  62. Pyjo/Server/CGI.py +259 -0
  63. Pyjo/Server/Daemon.py +1007 -0
  64. Pyjo/Server/FastCGI.py +517 -0
  65. Pyjo/Server/Hot/__init__.py +687 -0
  66. Pyjo/Server/Hot/__main__.py +5 -0
  67. Pyjo/Server/Lambda.py +641 -0
  68. Pyjo/Server/Prefork.py +917 -0
  69. Pyjo/Server/Prethread.py +394 -0
  70. Pyjo/Server/Reload/Backend/Poll.py +114 -0
  71. Pyjo/Server/Reload/Backend/__init__.py +126 -0
  72. Pyjo/Server/Reload/__init__.py +482 -0
  73. Pyjo/Server/Reload/__main__.py +5 -0
  74. Pyjo/Server/Stdio.py +228 -0
  75. Pyjo/Server/WSGI.py +349 -0
  76. Pyjo/Server/__init__.py +396 -0
  77. Pyjo/String/Bytes.py +532 -0
  78. Pyjo/String/HTML.py +210 -0
  79. Pyjo/String/Mixin.py +333 -0
  80. Pyjo/String/Template.py +470 -0
  81. Pyjo/String/Unicode.py +553 -0
  82. Pyjo/String/__init__.py +0 -0
  83. Pyjo/TOML.py +232 -0
  84. Pyjo/Template.py +1290 -0
  85. Pyjo/Test/Pyjo.py +1454 -0
  86. Pyjo/Test/__init__.py +1233 -0
  87. Pyjo/Test/pytest_plugin.py +305 -0
  88. Pyjo/Transaction/HTTP.py +374 -0
  89. Pyjo/Transaction/WebSocket.py +709 -0
  90. Pyjo/Transaction/__init__.py +429 -0
  91. Pyjo/URL/Template.py +459 -0
  92. Pyjo/URL/__init__.py +651 -0
  93. Pyjo/Upload.py +139 -0
  94. Pyjo/UserAgent/CookieJar.py +391 -0
  95. Pyjo/UserAgent/Proxy.py +138 -0
  96. Pyjo/UserAgent/Server.py +239 -0
  97. Pyjo/UserAgent/Transactor.py +766 -0
  98. Pyjo/UserAgent/__init__.py +1243 -0
  99. Pyjo/Util.py +3567 -0
  100. Pyjo/WebSocket.py +332 -0
  101. Pyjo/YAML.py +1167 -0
  102. Pyjo/__init__.py +441 -0
  103. Pyjo/__main__.py +35 -0
  104. Pyjo/py.typed +0 -0
  105. Pyjoyment/Command/Author/__init__.py +20 -0
  106. Pyjoyment/Command/Author/generate/__init__.py +106 -0
  107. Pyjoyment/Command/Author/generate/app.py +299 -0
  108. Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
  109. Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
  110. Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
  111. Pyjoyment/Command/Author/generate/lite_app.py +113 -0
  112. Pyjoyment/Command/Author/generate/plugin.py +267 -0
  113. Pyjoyment/Command/Author/generate/pyproject.py +123 -0
  114. Pyjoyment/Command/Author/inflate.py +101 -0
  115. Pyjoyment/Command/__init__.py +332 -0
  116. Pyjoyment/Command/asgi.py +92 -0
  117. Pyjoyment/Command/cgi.py +90 -0
  118. Pyjoyment/Command/daemon.py +160 -0
  119. Pyjoyment/Command/eval.py +128 -0
  120. Pyjoyment/Command/fastcgi.py +99 -0
  121. Pyjoyment/Command/get.py +305 -0
  122. Pyjoyment/Command/lambda.py +117 -0
  123. Pyjoyment/Command/prefork.py +181 -0
  124. Pyjoyment/Command/prethread.py +126 -0
  125. Pyjoyment/Command/routes.py +133 -0
  126. Pyjoyment/Command/stdio.py +92 -0
  127. Pyjoyment/Command/version.py +141 -0
  128. Pyjoyment/Command/wsgi.py +93 -0
  129. Pyjoyment/Commands.py +546 -0
  130. Pyjoyment/Controller.py +1307 -0
  131. Pyjoyment/HelloWorld.py +56 -0
  132. Pyjoyment/Lite.py +494 -0
  133. Pyjoyment/Plugin/AllowedHosts.py +205 -0
  134. Pyjoyment/Plugin/Config.py +248 -0
  135. Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
  136. Pyjoyment/Plugin/EPLRenderer.py +134 -0
  137. Pyjoyment/Plugin/EPRenderer.py +212 -0
  138. Pyjoyment/Plugin/HeaderCondition.py +98 -0
  139. Pyjoyment/Plugin/Health.py +325 -0
  140. Pyjoyment/Plugin/JSONConfig.py +172 -0
  141. Pyjoyment/Plugin/Mount.py +223 -0
  142. Pyjoyment/Plugin/PyRenderer.py +209 -0
  143. Pyjoyment/Plugin/TOMLConfig.py +141 -0
  144. Pyjoyment/Plugin/TagHelpers.py +959 -0
  145. Pyjoyment/Plugin/YAMLConfig.py +178 -0
  146. Pyjoyment/Plugin/__init__.py +80 -0
  147. Pyjoyment/Plugins.py +215 -0
  148. Pyjoyment/Renderer.py +673 -0
  149. Pyjoyment/Routes/Match.py +251 -0
  150. Pyjoyment/Routes/Pattern.py +443 -0
  151. Pyjoyment/Routes/Route.py +827 -0
  152. Pyjoyment/Routes/__init__.py +419 -0
  153. Pyjoyment/Sessions.py +255 -0
  154. Pyjoyment/Static.py +421 -0
  155. Pyjoyment/Types.py +238 -0
  156. Pyjoyment/Validator/Validation.py +395 -0
  157. Pyjoyment/Validator/__init__.py +304 -0
  158. Pyjoyment/__init__.py +1083 -0
  159. Pyjoyment/__main__.py +5 -0
  160. Pyjoyment/py.typed +0 -0
  161. Pyjoyment/resources/public/favicon.ico +0 -0
  162. Pyjoyment/resources/public/favicon.svg +345 -0
  163. Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
  164. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
  165. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
  166. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
  167. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
  168. Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
  169. Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
  170. Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
  171. Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
  172. Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
  173. Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
  174. Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
  175. Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
  176. Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
  177. Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
  178. Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
  179. Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
  180. Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
  181. Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
  182. Pyjoyment/resources/public/pyjo/logo.png +0 -0
  183. Pyjoyment/resources/public/pyjo/logo.svg +346 -0
  184. Pyjoyment/resources/public/pyjo/notfound.png +0 -0
  185. Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
  186. Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
  187. Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
  188. Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
  189. Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
  190. Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
  191. Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
  192. Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
  193. Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
  194. Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
  195. Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
  196. pyjoyment-0.1.0.dist-info/METADATA +376 -0
  197. pyjoyment-0.1.0.dist-info/RECORD +200 -0
  198. pyjoyment-0.1.0.dist-info/WHEEL +4 -0
  199. pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
  200. 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