Pyjoyment 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
Pyjo/Server/__init__.py
ADDED
|
@@ -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
|