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
Pyjo/App/__init__.py ADDED
@@ -0,0 +1,409 @@
1
+ """
2
+ Pyjo.App - Duct tape for the HTML5 web
3
+ ======================================
4
+ ::
5
+
6
+ from Pyjo.App import App
7
+
8
+ class MyApp(App):
9
+
10
+ # All the complexities of CGI, WSGI, ASGI, HTTP and WebSockets get
11
+ # reduced to a single method call!
12
+ def handler(self, tx):
13
+
14
+ # Resume transaction at the end of the block
15
+ with tx:
16
+
17
+ # Request
18
+ method = tx.req.method
19
+ path = tx.req.url.path
20
+
21
+ # Response
22
+ tx.res.code = 200
23
+ tx.res.headers.content_type = 'text/plain'
24
+ tx.res.body = f'{method} request for {path}!'.encode()
25
+
26
+ # Or import the module, as in Google Python Style Guide
27
+
28
+ import Pyjo.App
29
+
30
+ class MyApp(Pyjo.App.object):
31
+
32
+ # All the complexities of CGI, WSGI, ASGI, HTTP and WebSockets get
33
+ # reduced to a single method call!
34
+ def handler(self, tx):
35
+
36
+ # Resume transaction at the end of the block
37
+ with tx:
38
+
39
+ # Request
40
+ method = tx.req.method
41
+ path = tx.req.url.path
42
+
43
+ # Response
44
+ tx.res.code = 200
45
+ tx.res.headers.content_type = 'text/plain'
46
+ tx.res.body = f'{method} request for {path}!'.encode()
47
+
48
+ :mod:`Pyjo.App` is the base class of applications of :mod:`Pyjo`, a flexible
49
+ runtime environment for real-time web frameworks, with all the basic tools and
50
+ helpers needed to write simple web applications and higher level web
51
+ frameworks, such as :mod:`Pyjoyment`.
52
+
53
+ The end of a ``with`` block closes the connections and the servers of its
54
+ :attr:`~Pyjo_App.ua`.
55
+
56
+ See also
57
+ --------
58
+
59
+ :mod:`Pyjo.App.WSGI`, :mod:`Pyjo.HelloWorld`, :mod:`Pyjoyment`, https://mojolicious.org.
60
+
61
+ Inspired by `Mojo <https://github.com/mojolicious/mojo/blob/v6.0/lib/Mojo.pm>`_ of Mojolicious 6.0, the base class of
62
+ Mojolicious until Mojolicious 7.
63
+ """
64
+
65
+ import Pyjo.Base
66
+ import Pyjo.Home
67
+ import Pyjo.Log
68
+ import Pyjo.Promise
69
+ import Pyjo.Server
70
+ import Pyjo.Transaction.HTTP
71
+ import Pyjo.UserAgent
72
+ import Pyjo.Util
73
+
74
+ import os
75
+ import sys
76
+
77
+ from types import TracebackType
78
+ from typing import Any, Self, overload
79
+
80
+
81
+ class Pyjo_App(Pyjo.Base.object):
82
+ """
83
+ :mod:`Pyjo.App` inherits all methods from :mod:`Pyjo.Base` and implements
84
+ the following new ones.
85
+ """
86
+
87
+ # The methods that keep a stash, whose values the arguments of the constructor are
88
+ _stashes: tuple[str, ...] = ('config',)
89
+
90
+ def __init__(self, attrs: dict[str, Any] | None = None, /, **kwargs: Any) -> None:
91
+ """::
92
+
93
+ app = MyApp.new()
94
+ app = MyApp.new(moniker='foo_bar')
95
+ app = MyApp.new({'moniker': 'foo_bar'})
96
+
97
+ Construct a new application, with the attributes as keyword arguments
98
+ or a dict, and the arguments of
99
+ :meth:`Pyjo.Server.Pyjo_Server.load_app` for its script. The value of
100
+ ``config`` is the dict of :meth:`config`.
101
+ """
102
+ self._home: Pyjo.Home.object | None = None
103
+ self._log: Pyjo.Log.object | None = None
104
+ self._moniker: str | None = None
105
+ self._ua: Pyjo.UserAgent.object | None = None
106
+
107
+ # Not in Mojolicious: the value of a method that keeps a stash, as config, is its dict, as Mojo keeps the stash in
108
+ # the hash of the object, which the arguments of new fill
109
+ for name, value in {**(attrs or {}), **kwargs, **Pyjo.Server.args_override}.items():
110
+ setattr(self, f'_{name}' if name in self._stashes else name, value)
111
+
112
+ # Not in Mojolicious: a context manager, which closes the connections and the servers of its user agent where the
113
+ # block ends, as PyPy frees the object only when its garbage collector runs
114
+ def __enter__(self) -> Self:
115
+ return self
116
+
117
+ def __exit__(
118
+ self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
119
+ ) -> None:
120
+ self.close()
121
+
122
+ @property
123
+ def home(self) -> Pyjo.Home.object:
124
+ """::
125
+
126
+ home = app.home
127
+ app.home = Pyjo.Home.new()
128
+
129
+ The home directory of your application, defaults to a :mod:`Pyjo.Home`
130
+ object which stringifies to the actual path. ::
131
+
132
+ # Portably generate path relative to home directory
133
+ path = app.home.child('data', 'important.txt')
134
+ """
135
+ if self._home is None:
136
+ self._home = Pyjo.Home.new().detect(type(self))
137
+ return self._home
138
+
139
+ @home.setter
140
+ def home(self, value: Pyjo.Home.object) -> None:
141
+ self._home = value
142
+
143
+ @property
144
+ def log(self) -> Pyjo.Log.object:
145
+ """::
146
+
147
+ log = app.log
148
+ app.log = Pyjo.Log.new()
149
+
150
+ The logging layer of your application, defaults to a :mod:`Pyjo.Log`
151
+ object. The level will default to the ``PYJO_LOG_LEVEL`` environment
152
+ variable, or ``trace``, and the messages will be written to
153
+ ``sys.stderr``. ::
154
+
155
+ # Log debug message
156
+ app.log.debug('It works')
157
+ """
158
+ if self._log is None:
159
+ log = Pyjo.Log.new()
160
+ # The level of the environment, as an application of Pyjoyment has it, which the tests of Pyjo.Test.Pyjo
161
+ # set, so that an application of Pyjo, as an adapter of another framework, logs as quietly
162
+ if level := os.environ.get('PYJO_LOG_LEVEL'):
163
+ log.level = level
164
+ self._log = log
165
+ return self._log
166
+
167
+ @log.setter
168
+ def log(self, value: Pyjo.Log.object) -> None:
169
+ self._log = value
170
+
171
+ @property
172
+ def moniker(self) -> str:
173
+ """::
174
+
175
+ moniker = app.moniker
176
+ app.moniker = 'foo_bar'
177
+
178
+ Moniker of this application, often used as default filename for
179
+ configuration files and the like, defaults to decamelizing the
180
+ application class with :func:`Pyjo.Util.decamelize`: the module of a
181
+ class that the module names ``object``, as ``pyjo-hello_world`` of
182
+ :mod:`Pyjo.HelloWorld`, or the class in its module.
183
+ """
184
+ if self._moniker is None:
185
+ self._moniker = Pyjo.Util.decamelize(_package(type(self)).replace('.', '::'))
186
+ return self._moniker
187
+
188
+ @moniker.setter
189
+ def moniker(self, value: str) -> None:
190
+ self._moniker = value
191
+
192
+ @property
193
+ def ua(self) -> Pyjo.UserAgent.object:
194
+ """::
195
+
196
+ ua = app.ua
197
+ app.ua = Pyjo.UserAgent.new()
198
+
199
+ A full featured HTTP user agent for use in your applications, defaults
200
+ to a :mod:`Pyjo.UserAgent` object. ::
201
+
202
+ # Perform blocking request
203
+ print(app.ua.get('example.com').result.body)
204
+ """
205
+ if self._ua is None:
206
+ self._ua = Pyjo.UserAgent.new()
207
+ return self._ua
208
+
209
+ @ua.setter
210
+ def ua(self, value: Pyjo.UserAgent.object) -> None:
211
+ self._ua = value
212
+
213
+ def build_tx(self) -> Pyjo.Transaction.HTTP.object:
214
+ """::
215
+
216
+ tx = app.build_tx()
217
+
218
+ Transaction builder, defaults to building a
219
+ :mod:`Pyjo.Transaction.HTTP` object.
220
+ """
221
+ return Pyjo.Transaction.HTTP.new()
222
+
223
+ # Not in Mojolicious: the release of the user agent, which Perl does in DESTROY, also at the end of a with block
224
+ def close(self) -> None:
225
+ """::
226
+
227
+ app.close()
228
+
229
+ Close the connections and the servers of :attr:`ua`, as the end of a
230
+ ``with`` block does.
231
+ """
232
+ if self._ua is not None:
233
+ self._ua.close()
234
+
235
+ # config(**values) with no values is config(), which the first overload types
236
+ @overload
237
+ def config(self) -> dict[str, Any]: ... # pyright: ignore[reportOverlappingOverload]
238
+
239
+ @overload
240
+ def config(self, name: str, /) -> Any: ...
241
+
242
+ @overload
243
+ def config(self, values: dict[str, Any], /) -> Self: ...
244
+
245
+ @overload
246
+ def config(self, **values: Any) -> Self: ...
247
+
248
+ def config(self, *args: Any, **values: Any) -> Any:
249
+ """::
250
+
251
+ hash = app.config()
252
+ foo = app.config('foo')
253
+ app = app.config({'foo': 'bar', 'baz': 23})
254
+ app = app.config(foo='bar', baz=23)
255
+
256
+ Application configuration. ::
257
+
258
+ # Remove value
259
+ foo = app.config().pop('foo')
260
+
261
+ # Assign multiple values at once
262
+ app.config(foo='test', bar=23)
263
+ """
264
+ return Pyjo.Util._stash('config', self, args, values) # pyright: ignore[reportPrivateUsage]
265
+
266
+ def handler(self, tx: Any) -> None:
267
+ """::
268
+
269
+ app.handler(Pyjo.Transaction.HTTP.new())
270
+
271
+ The handler is the main entry point to your application or framework
272
+ and will be called for each new transaction, which will usually be a
273
+ :mod:`Pyjo.Transaction.HTTP` or :mod:`Pyjo.Transaction.WebSocket`
274
+ object. A route of :mod:`Pyjoyment` that embeds the application, as
275
+ :mod:`Pyjoyment.Plugin.Mount` does, calls it with its controller
276
+ instead, whose ``tx`` is the transaction and whose stash has the rest of
277
+ the path in ``path``. Meant to be overloaded in a subclass. ::
278
+
279
+ def handler(self, tx):
280
+ ...
281
+ """
282
+ raise NotImplementedError('Method "handler" not implemented by subclass')
283
+
284
+ def server(self, server: Pyjo.Server.object) -> None:
285
+ """::
286
+
287
+ app.server(Pyjo.Server.new())
288
+
289
+ Called by the server before it starts. Meant to be overloaded in a
290
+ subclass.
291
+ """
292
+
293
+ # Not in Mojolicious: the end of a server, as server is its start, after which the application cleans up what it has
294
+ # started, as the lifespan of an ASGI application
295
+ def server_stopped(self, server: Pyjo.Server.object) -> 'Pyjo.Promise.object | None':
296
+ """::
297
+
298
+ promise = app.server_stopped(Pyjo.Server.new())
299
+
300
+ Called by the server once it has stopped gracefully, in each process
301
+ and thread that has run the application, with the event loop no longer
302
+ running. A promise that it returns is waited for, so that the
303
+ application can clean up with the event loop. Meant to be overloaded
304
+ in a subclass.
305
+ """
306
+ return None
307
+
308
+ def start(self, *args: str) -> Self:
309
+ """::
310
+
311
+ app = app.start()
312
+
313
+ Start the application. When the environment variable
314
+ ``PYJO_APP_LOADER`` is set, as :meth:`Pyjo.Server.Pyjo_Server.load_app`
315
+ does, it is kept in :data:`Pyjo.Server.loaded_app` for the server.
316
+ """
317
+ # Application loader
318
+ if os.environ.get('PYJO_APP_LOADER') is not None:
319
+ Pyjo.Server.loaded_app = self
320
+ return self
321
+
322
+
323
+ # The name of a class as Mojo has the package: the module that names the class its object, or the class in the module,
324
+ # and the class only in a script
325
+ def _package(cls: type) -> str:
326
+ module = cls.__module__
327
+ if getattr(sys.modules.get(module), 'object', None) is cls:
328
+ return module
329
+ return cls.__qualname__ if module == '__main__' else f'{module}.{cls.__qualname__}'
330
+
331
+
332
+ # The path of the request for an application of another framework, as escaped strings: the path that a route embeds the
333
+ # application under, after the base path, as SCRIPT_NAME of WSGI and root_path of ASGI, and the rest of it, which the
334
+ # route left in the stash
335
+ def _mount(tx: Any, c: Any) -> tuple[str, str]: # pyright: ignore[reportUnusedFunction]
336
+ url = tx.req.url
337
+ path = url.path.to_abs_str()
338
+ script = url.base.path.to_str().rstrip('/')
339
+
340
+ if c is not None and (rest := c.stash().get('path')) is not None:
341
+ route = url.path.to_route()
342
+ if not route.endswith(rest):
343
+ path = rest
344
+ else:
345
+ # The rest begins at the slash after the ones of the path above it
346
+ end = len(path)
347
+ if len(rest):
348
+ end = -1
349
+ for _ in range(route[: len(route) - len(rest)].count('/') + 1):
350
+ if (end := path.find('/', end + 1)) < 0:
351
+ end = len(path)
352
+ break
353
+ script += path[:end]
354
+ path = path[end:]
355
+
356
+ return script, path
357
+
358
+
359
+ # Not in Mojolicious: a route embeds an application of another framework at a part of the path that ends with a slash,
360
+ # or at the whole path, as SCRIPT_NAME of CGI and WSGI and root_path of ASGI can only be, where /php of /phpx is not
361
+ def _within(c: Any) -> bool: # pyright: ignore[reportUnusedFunction]
362
+ return c is None or not (rest := c.stash().get('path')) or rest.startswith('/')
363
+
364
+
365
+ # The environment of CGI for the request, as an application of another framework gets it: the path that a route embeds
366
+ # the application under is SCRIPT_NAME, and the rest of it is PATH_INFO, as unescaped bytes in the characters 0-255
367
+ def _cgi(tx: Any, c: Any) -> dict[str, str]: # pyright: ignore[reportUnusedFunction]
368
+ req = tx.req
369
+ url = req.url
370
+ base = url.base
371
+ script, path = _mount(tx, c)
372
+
373
+ https = req.is_secure
374
+ env = {
375
+ 'REQUEST_METHOD': req.method,
376
+ 'SCRIPT_NAME': _bytes(script),
377
+ 'PATH_INFO': _bytes(path),
378
+ 'QUERY_STRING': url.query.to_str(),
379
+ 'SERVER_NAME': base.host or tx.local_address or 'localhost',
380
+ 'SERVER_PORT': str(base.port or tx.local_port or (443 if https else 80)),
381
+ 'SERVER_PROTOCOL': f'HTTP/{req.version}',
382
+ }
383
+ if (address := tx.remote_address) is not None:
384
+ env['REMOTE_ADDR'] = address
385
+ if (port := tx.remote_port) is not None:
386
+ env['REMOTE_PORT'] = str(port)
387
+
388
+ # Headers, joined as CGI has them, and the length of the body, which is whole, as it has been read
389
+ headers = req.headers
390
+ for name in headers.names():
391
+ key = name.upper().replace('-', '_')
392
+ if key == 'CONTENT_TYPE':
393
+ env[key] = headers.header(name)
394
+ elif key not in ('CONTENT_LENGTH', 'TRANSFER_ENCODING'):
395
+ env[f'HTTP_{key}'] = ('; ' if key == 'COOKIE' else ', ').join(headers.every_header(name))
396
+ if size := req.content.body_size:
397
+ env['CONTENT_LENGTH'] = str(size)
398
+
399
+ return env
400
+
401
+
402
+ # A path of CGI: the unescaped bytes as the characters 0-255
403
+ def _bytes(path: str) -> str:
404
+ return Pyjo.Util.url_unescape(path.encode()).decode('latin-1')
405
+
406
+
407
+ new = Pyjo_App.new
408
+ object = Pyjo_App
409
+ App = Pyjo_App