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
@@ -0,0 +1,396 @@
1
+ """
2
+ Pyjo.Server - HTTP/WebSocket server base class
3
+ ==============================================
4
+ ::
5
+
6
+ from Pyjo.Server import Server
7
+
8
+ class Pyjo_Server_MyServer(Server):
9
+ def run(self):
10
+ # Get a transaction
11
+ tx = self.build_tx()
12
+
13
+ # Emit "request" event
14
+ self.emit('request', tx)
15
+
16
+ # Or import the module, as in Google Python Style Guide
17
+
18
+ import Pyjo.Server
19
+
20
+ class Pyjo_Server_MyServer(Pyjo.Server.object):
21
+ def run(self):
22
+ # Get a transaction
23
+ tx = self.build_tx()
24
+
25
+ # Emit "request" event
26
+ self.emit('request', tx)
27
+
28
+ :mod:`Pyjo.Server` is an abstract base class for HTTP/WebSocket servers and
29
+ server interfaces, like :mod:`Pyjo.Server.ASGI`, :mod:`Pyjo.Server.CGI`,
30
+ :mod:`Pyjo.Server.Daemon` and :mod:`Pyjo.Server.WSGI`.
31
+
32
+ A server of another distribution is a module in the namespace
33
+ ``Pyjo.Server``, which :func:`pkgutil.extend_path` keeps open, as for
34
+ :mod:`Pyjo.Reactor`.
35
+
36
+ Events
37
+ ------
38
+
39
+ :mod:`Pyjo.Server` inherits all events from :mod:`Pyjo.EventEmitter` and can
40
+ emit the following new ones.
41
+
42
+ request
43
+ ~~~~~~~
44
+ ::
45
+
46
+ server.on('request', lambda server, tx: ...)
47
+
48
+ Emitted when a request is ready and needs to be handled. ::
49
+
50
+ @server.on('request')
51
+ def request(server, tx):
52
+ tx.res.code = 200
53
+ tx.res.headers.content_type = 'text/plain'
54
+ tx.res.body = b'Hello World!'
55
+ tx.resume()
56
+
57
+ See also
58
+ --------
59
+
60
+ :mod:`Pyjoyment`, https://mojolicious.org.
61
+
62
+ Ported from `Mojo::Server <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Server.pm>`_
63
+ of Mojolicious 9.49.
64
+
65
+ Classes
66
+ -------
67
+ """
68
+
69
+ import Pyjo.EventEmitter
70
+ import Pyjo.Exception
71
+ import Pyjo.File
72
+ import Pyjo.Log
73
+ import Pyjo.Transaction
74
+ import Pyjo.Transaction.HTTP
75
+
76
+ from Pyjo.Loader import load_class
77
+ from Pyjo.String.Unicode import split
78
+ from Pyjo.Util import md5_sum
79
+
80
+ import builtins
81
+ import importlib
82
+ import importlib.machinery
83
+ import importlib.util
84
+ import os
85
+ import sys
86
+
87
+ from pkgutil import extend_path
88
+ from typing import Any, NoReturn, Protocol
89
+
90
+
91
+ type Anything = builtins.object
92
+
93
+
94
+ class Application(Protocol):
95
+ """
96
+ What a server needs from the application it handles.
97
+ """
98
+
99
+ @property
100
+ def log(self) -> Pyjo.Log.object: ...
101
+
102
+ def build_tx(self) -> Pyjo.Transaction.HTTP.object: ...
103
+
104
+ def handler(self, tx: Pyjo.Transaction.HTTP.object) -> Anything: ...
105
+
106
+ def server(self, server: 'Pyjo_Server') -> Anything: ...
107
+
108
+ def server_stopped(self, server: 'Pyjo_Server') -> Anything: ...
109
+
110
+
111
+ # Not in Mojolicious: a namespace that other distributions can add servers to
112
+ __path__ = extend_path(__path__, __name__)
113
+
114
+ # Renamed from Mojolicious: @ARGS_OVERRIDE, a variable of the module that changes
115
+ args_override: dict[str, Any] = {}
116
+ """
117
+ The arguments of :meth:`~Pyjo_Server.load_app` for the constructor of the
118
+ application that the script builds.
119
+ """
120
+
121
+ # Not in Mojolicious: the application that the script of load_app starts, where Mojo takes the value of the script
122
+ loaded_app: Application | None = None
123
+ """
124
+ The application that the script of :meth:`~Pyjo_Server.load_app` starts:
125
+ its ``start`` keeps itself here instead of running a command, when the
126
+ environment variable ``PYJO_APP_LOADER`` is set.
127
+ """
128
+
129
+
130
+ class Pyjo_Server(Pyjo.EventEmitter.object):
131
+ """
132
+ :mod:`Pyjo.Server` inherits all methods from :mod:`Pyjo.EventEmitter` and
133
+ implements the following new ones.
134
+ """
135
+
136
+ def __init__(
137
+ self,
138
+ *,
139
+ app: Application | None = None,
140
+ reverse_proxy: bool | None = None,
141
+ trusted_proxies: list[str] | None = None,
142
+ ) -> None:
143
+ """::
144
+
145
+ server = Pyjo.Server.new()
146
+ server = Pyjo.Server.new(reverse_proxy=True)
147
+
148
+ Construct a new :mod:`Pyjo.Server` object and subscribe to
149
+ ``request`` event with default request handling.
150
+ """
151
+ super().__init__()
152
+
153
+ self._app = app
154
+
155
+ self._trusted_proxies: list[str] | None = trusted_proxies
156
+ self._reverse_proxy: bool | None = reverse_proxy
157
+
158
+ self.on('request', _request)
159
+
160
+ @property
161
+ def trusted_proxies(self) -> list[str]:
162
+ """::
163
+
164
+ proxies = server.trusted_proxies
165
+ server.trusted_proxies = ['10.0.0.0/8', '127.0.0.1', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7']
166
+
167
+ This server expects requests from trusted reverse proxies, defaults to
168
+ the value of the ``PYJO_TRUSTED_PROXIES`` environment variable split
169
+ on commas with optional whitespace. These proxies should be addresses
170
+ or networks in CIDR form.
171
+ """
172
+ if self._trusted_proxies is None:
173
+ self._trusted_proxies = [
174
+ proxy or '' for proxy in split(r'\s*,\s*', os.environ.get('PYJO_TRUSTED_PROXIES') or '')
175
+ ]
176
+ return self._trusted_proxies
177
+
178
+ @trusted_proxies.setter
179
+ def trusted_proxies(self, value: list[str]) -> None:
180
+ self._trusted_proxies = value
181
+
182
+ @property
183
+ def reverse_proxy(self) -> bool:
184
+ """::
185
+
186
+ boolean = server.reverse_proxy
187
+ server.reverse_proxy = boolean
188
+
189
+ This server operates behind a reverse proxy, defaults to the value of
190
+ the ``PYJO_REVERSE_PROXY`` environment variable or true if
191
+ :attr:`trusted_proxies` is not empty.
192
+ """
193
+ if self._reverse_proxy is None:
194
+ self._reverse_proxy = bool(os.environ.get('PYJO_REVERSE_PROXY')) or bool(self.trusted_proxies)
195
+ return self._reverse_proxy
196
+
197
+ @reverse_proxy.setter
198
+ def reverse_proxy(self, value: bool) -> None:
199
+ self._reverse_proxy = value
200
+
201
+ @property
202
+ def app(self) -> Application:
203
+ """::
204
+
205
+ app = server.app
206
+ server.app = MyApp.new()
207
+
208
+ Application this server handles, defaults to a :mod:`Pyjo.HelloWorld`
209
+ object.
210
+ """
211
+ if self._app is None:
212
+ # Unlike Mojolicious: the default is Pyjo.HelloWorld, an application of pure Pyjo, where Mojo::HelloWorld is one
213
+ # of Mojolicious, so a server does not load Pyjoyment
214
+ return self.build_app('Pyjo.HelloWorld')
215
+ return self._app
216
+
217
+ @app.setter
218
+ def app(self, value: Application) -> None:
219
+ self._app = value
220
+
221
+ def build_app(self, app: str | type, **kwargs: Any) -> Application:
222
+ """::
223
+
224
+ app = server.build_app('MyApp')
225
+ app = server.build_app('MyApp', log=Pyjo.Log.new())
226
+ app = server.build_app(MyApp)
227
+
228
+ Build application from class and assign it to :attr:`app`. The class
229
+ is a dotted name of a module with a ``new`` function, as the modules
230
+ of Pyjoyment have, or of a class in a module, or the class itself.
231
+ """
232
+ exe = os.environ.pop('PYJO_EXE', None)
233
+ try:
234
+ # Not in Mojolicious: a class of Python
235
+ if isinstance(app, type):
236
+ self.app = app(**kwargs)
237
+ return self.app
238
+ if not (e := load_class(app)):
239
+ self.app = sys.modules[app].new(**kwargs)
240
+ return self.app
241
+ if isinstance(e, Exception):
242
+ raise e
243
+ # Not in Mojolicious: a class in a module, as the longest prefix of the name that is a module
244
+ if (cls := _class(app)) is not None:
245
+ self.app = cls(**kwargs)
246
+ return self.app
247
+ raise Pyjo.Exception.new(f'Can\'t find application class "{app}" in sys.path. ({" ".join(sys.path)})\n')
248
+ finally:
249
+ if exe is not None:
250
+ os.environ['PYJO_EXE'] = exe
251
+
252
+ def build_tx(self) -> Pyjo.Transaction.HTTP.object:
253
+ """::
254
+
255
+ tx = server.build_tx()
256
+
257
+ Let application build a transaction.
258
+ """
259
+ tx = self.app.build_tx()
260
+ tx.req.trusted_proxies.extend(self.trusted_proxies)
261
+ if self.reverse_proxy:
262
+ tx.req.reverse_proxy = True
263
+ return tx
264
+
265
+ def daemonize(self) -> None:
266
+ """::
267
+
268
+ server.daemonize()
269
+
270
+ Daemonize server process.
271
+ """
272
+ # Fork and kill parent
273
+ try:
274
+ pid = os.fork()
275
+ except OSError as e:
276
+ _croak(e, "Can't fork")
277
+ if pid:
278
+ os._exit(0)
279
+ try:
280
+ os.setsid()
281
+ except OSError as e:
282
+ _croak(e, "Can't start a new session")
283
+
284
+ # Close filehandles
285
+ null = os.open(os.devnull, os.O_RDWR)
286
+ for handle in (sys.stdin, sys.stdout, sys.stderr):
287
+ os.dup2(null, handle.fileno())
288
+ os.close(null)
289
+
290
+ def load_app(self, path: str | os.PathLike[str], **kwargs: Any) -> Application:
291
+ """::
292
+
293
+ app = server.load_app('/home/sri/myapp.py')
294
+ app = server.load_app('/home/sri/myapp.py', log=Pyjo.Log.new())
295
+
296
+ Load application from script and assign it to :attr:`app`. The
297
+ script starts its application, as ``MyApp.new().start()``, and the
298
+ ``start`` method keeps it in :data:`loaded_app` instead of running a
299
+ command, as the environment variable ``PYJO_APP_LOADER`` is set. ::
300
+
301
+ print(Pyjo.Server.new().load_app('./myapp.py').log.level)
302
+ """
303
+ global args_override, loaded_app
304
+
305
+ # Clean environment
306
+ path = Pyjo.File.path(path).to_abs().to_str()
307
+ argv = sys.argv[:1]
308
+ loader, exe = os.environ.get('PYJO_APP_LOADER'), os.environ.pop('PYJO_EXE', None)
309
+ args, loaded = args_override, loaded_app
310
+ # Left out from Mojolicious: FindBin, which Python does not have
311
+ try:
312
+ sys.argv[:1] = [path]
313
+ os.environ['PYJO_APP_LOADER'] = '1'
314
+ args_override, loaded_app = kwargs, None
315
+
316
+ # Try to load application from script into sandbox
317
+ try:
318
+ _run(path, f'Pyjo.Server.Sandbox.{md5_sum(path.encode())}')
319
+ except Exception as e:
320
+ raise Pyjo.Exception.new(f'Can\'t load application from file "{path}": {e}') from e
321
+ if (app := loaded_app) is None or not callable(getattr(app, 'handler', None)):
322
+ raise Pyjo.Exception.new(f'File "{path}" did not return an application object.\n')
323
+ self.app = app
324
+ finally:
325
+ sys.argv[:1] = argv
326
+ if loader is None:
327
+ os.environ.pop('PYJO_APP_LOADER', None)
328
+ else:
329
+ os.environ['PYJO_APP_LOADER'] = loader
330
+ if exe is not None:
331
+ os.environ['PYJO_EXE'] = exe
332
+ args_override, loaded_app = args, loaded
333
+
334
+ return self.app
335
+
336
+ def run(self) -> Anything:
337
+ """::
338
+
339
+ server.run()
340
+
341
+ Run server. Meant to be overloaded in a subclass.
342
+ """
343
+ raise NotImplementedError('Method "run" not implemented by subclass')
344
+
345
+ # Not in Mojolicious: the end of the server, which the application learns as it learns its start, and whose cleanup
346
+ # the server waits for, with the event loop
347
+ def _server_stopped(self) -> None:
348
+ import Pyjo.Promise
349
+
350
+ if isinstance(promise := self.app.server_stopped(self), Pyjo.Promise.object):
351
+ promise.wait()
352
+
353
+
354
+ # Not in Mojolicious: the script as a module of its own with a loader, where runpy.run_path gives it none, so that
355
+ # linecache finds its source for a stack trace, as asyncio in debug mode makes them, without the DeprecationWarning of
356
+ # Python 3.15; a script without the suffix .py runs too, and no bytecode is written next to it
357
+ def _run(path: str, name: str) -> None:
358
+ loader = importlib.machinery.SourceFileLoader(name, path)
359
+ module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec(name, loader, origin=path))
360
+ module.__file__ = path
361
+
362
+ # The sandbox is a module that is loaded, as the package of Mojo is global, where the classes of the script find
363
+ # it, as the DATA section of an application of Pyjoyment.Lite
364
+ sys.modules[name] = module
365
+ exec(compile(loader.get_source(name) or '', path, 'exec'), module.__dict__)
366
+
367
+
368
+ def _request(server: Pyjo_Server, tx: Pyjo.Transaction.HTTP.object) -> None:
369
+ server.app.handler(tx)
370
+
371
+
372
+ def _class(name: str) -> Any:
373
+ parts = name.split('.')
374
+ for i in range(len(parts) - 1, 0, -1):
375
+ try:
376
+ found: Any = importlib.import_module('.'.join(parts[:i]))
377
+ except ImportError:
378
+ continue
379
+ try:
380
+ for attr in parts[i:]:
381
+ found = getattr(found, attr)
382
+ except AttributeError:
383
+ return None
384
+ return found if isinstance(found, type) else None
385
+ return None
386
+
387
+
388
+ def _croak(e: OSError, message: str) -> NoReturn:
389
+ error = type(e)(f'{message}: {e.strerror}' if e.strerror else message)
390
+ error.errno = e.errno
391
+ raise error from e
392
+
393
+
394
+ new = Pyjo_Server.new
395
+ object = Pyjo_Server
396
+ Server = Pyjo_Server