plain 0.75.0__py3-none-any.whl → 0.76.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.
- plain/CHANGELOG.md +19 -0
- plain/cli/core.py +35 -17
- plain/cli/runtime.py +28 -0
- plain/cli/server.py +143 -0
- plain/server/LICENSE +35 -0
- plain/server/README.md +75 -0
- plain/server/__init__.py +9 -0
- plain/server/app.py +52 -0
- plain/server/arbiter.py +555 -0
- plain/server/config.py +118 -0
- plain/server/errors.py +31 -0
- plain/server/glogging.py +292 -0
- plain/server/http/__init__.py +12 -0
- plain/server/http/body.py +283 -0
- plain/server/http/errors.py +150 -0
- plain/server/http/message.py +399 -0
- plain/server/http/parser.py +69 -0
- plain/server/http/unreader.py +88 -0
- plain/server/http/wsgi.py +421 -0
- plain/server/pidfile.py +91 -0
- plain/server/reloader.py +158 -0
- plain/server/sock.py +219 -0
- plain/server/util.py +380 -0
- plain/server/workers/__init__.py +12 -0
- plain/server/workers/base.py +305 -0
- plain/server/workers/gthread.py +393 -0
- plain/server/workers/sync.py +210 -0
- plain/server/workers/workertmp.py +50 -0
- {plain-0.75.0.dist-info → plain-0.76.0.dist-info}/METADATA +1 -1
- {plain-0.75.0.dist-info → plain-0.76.0.dist-info}/RECORD +33 -7
- {plain-0.75.0.dist-info → plain-0.76.0.dist-info}/WHEEL +0 -0
- {plain-0.75.0.dist-info → plain-0.76.0.dist-info}/entry_points.txt +0 -0
- {plain-0.75.0.dist-info → plain-0.76.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of gunicorn released under the MIT license.
|
6
|
+
# See the LICENSE for more information.
|
7
|
+
#
|
8
|
+
# Vendored and modified for Plain.
|
9
|
+
import errno
|
10
|
+
import os
|
11
|
+
import select
|
12
|
+
import socket
|
13
|
+
import ssl
|
14
|
+
import sys
|
15
|
+
from datetime import datetime
|
16
|
+
from typing import Any
|
17
|
+
|
18
|
+
from .. import http, sock, util
|
19
|
+
from ..http import wsgi
|
20
|
+
from . import base
|
21
|
+
|
22
|
+
|
23
|
+
class StopWaiting(Exception):
|
24
|
+
"""exception raised to stop waiting for a connection"""
|
25
|
+
|
26
|
+
|
27
|
+
class SyncWorker(base.Worker):
|
28
|
+
def accept(self, listener: socket.socket) -> None:
|
29
|
+
client, addr = listener.accept()
|
30
|
+
client.setblocking(True)
|
31
|
+
util.close_on_exec(client.fileno())
|
32
|
+
self.handle(listener, client, addr)
|
33
|
+
|
34
|
+
def wait(self, timeout: float) -> list[Any] | None:
|
35
|
+
try:
|
36
|
+
self.notify()
|
37
|
+
ret = select.select(self.wait_fds, [], [], timeout)
|
38
|
+
if ret[0]:
|
39
|
+
if self.PIPE[0] in ret[0]:
|
40
|
+
os.read(self.PIPE[0], 1)
|
41
|
+
return ret[0]
|
42
|
+
return None
|
43
|
+
|
44
|
+
except OSError as e:
|
45
|
+
if e.args[0] == errno.EINTR:
|
46
|
+
return self.sockets
|
47
|
+
if e.args[0] == errno.EBADF:
|
48
|
+
if self.nr < 0:
|
49
|
+
return self.sockets
|
50
|
+
else:
|
51
|
+
raise StopWaiting
|
52
|
+
raise
|
53
|
+
|
54
|
+
def is_parent_alive(self) -> bool:
|
55
|
+
# If our parent changed then we shut down.
|
56
|
+
if self.ppid != os.getppid():
|
57
|
+
self.log.info("Parent changed, shutting down: %s", self)
|
58
|
+
return False
|
59
|
+
return True
|
60
|
+
|
61
|
+
def run_for_one(self, timeout: float) -> None:
|
62
|
+
listener = self.sockets[0]
|
63
|
+
while self.alive:
|
64
|
+
self.notify()
|
65
|
+
|
66
|
+
# Accept a connection. If we get an error telling us
|
67
|
+
# that no connection is waiting we fall down to the
|
68
|
+
# select which is where we'll wait for a bit for new
|
69
|
+
# workers to come give us some love.
|
70
|
+
try:
|
71
|
+
self.accept(listener)
|
72
|
+
# Keep processing clients until no one is waiting. This
|
73
|
+
# prevents the need to select() for every client that we
|
74
|
+
# process.
|
75
|
+
continue
|
76
|
+
|
77
|
+
except OSError as e:
|
78
|
+
if e.errno not in (errno.EAGAIN, errno.ECONNABORTED, errno.EWOULDBLOCK):
|
79
|
+
raise
|
80
|
+
|
81
|
+
if not self.is_parent_alive():
|
82
|
+
return None
|
83
|
+
|
84
|
+
try:
|
85
|
+
self.wait(timeout)
|
86
|
+
except StopWaiting:
|
87
|
+
return None
|
88
|
+
|
89
|
+
def run_for_multiple(self, timeout: float) -> None:
|
90
|
+
while self.alive:
|
91
|
+
self.notify()
|
92
|
+
|
93
|
+
try:
|
94
|
+
ready = self.wait(timeout)
|
95
|
+
except StopWaiting:
|
96
|
+
return None
|
97
|
+
|
98
|
+
if ready is not None:
|
99
|
+
for listener in ready:
|
100
|
+
if listener == self.PIPE[0]:
|
101
|
+
continue
|
102
|
+
|
103
|
+
try:
|
104
|
+
self.accept(listener)
|
105
|
+
except OSError as e:
|
106
|
+
if e.errno not in (
|
107
|
+
errno.EAGAIN,
|
108
|
+
errno.ECONNABORTED,
|
109
|
+
errno.EWOULDBLOCK,
|
110
|
+
):
|
111
|
+
raise
|
112
|
+
|
113
|
+
if not self.is_parent_alive():
|
114
|
+
return None
|
115
|
+
|
116
|
+
def run(self) -> None:
|
117
|
+
# if no timeout is given the worker will never wait and will
|
118
|
+
# use the CPU for nothing. This minimal timeout prevent it.
|
119
|
+
timeout = self.timeout or 0.5
|
120
|
+
|
121
|
+
# self.socket appears to lose its blocking status after
|
122
|
+
# we fork in the arbiter. Reset it here.
|
123
|
+
for s in self.sockets:
|
124
|
+
s.setblocking(False)
|
125
|
+
|
126
|
+
if len(self.sockets) > 1:
|
127
|
+
self.run_for_multiple(timeout)
|
128
|
+
else:
|
129
|
+
self.run_for_one(timeout)
|
130
|
+
|
131
|
+
def handle(self, listener: socket.socket, client: socket.socket, addr: Any) -> None:
|
132
|
+
req = None
|
133
|
+
try:
|
134
|
+
if self.cfg.is_ssl:
|
135
|
+
client = sock.ssl_wrap_socket(client, self.cfg)
|
136
|
+
parser = http.RequestParser(self.cfg, client, addr)
|
137
|
+
req = next(parser)
|
138
|
+
self.handle_request(listener, req, client, addr)
|
139
|
+
except http.errors.NoMoreData as e:
|
140
|
+
self.log.debug("Ignored premature client disconnection. %s", e)
|
141
|
+
except StopIteration as e:
|
142
|
+
self.log.debug("Closing connection. %s", e)
|
143
|
+
except ssl.SSLError as e:
|
144
|
+
if e.args[0] == ssl.SSL_ERROR_EOF:
|
145
|
+
self.log.debug("ssl connection closed")
|
146
|
+
client.close()
|
147
|
+
else:
|
148
|
+
self.log.debug("Error processing SSL request.")
|
149
|
+
self.handle_error(req, client, addr, e)
|
150
|
+
except OSError as e:
|
151
|
+
if e.errno not in (errno.EPIPE, errno.ECONNRESET, errno.ENOTCONN):
|
152
|
+
self.log.exception("Socket error processing request.")
|
153
|
+
else:
|
154
|
+
if e.errno == errno.ECONNRESET:
|
155
|
+
self.log.debug("Ignoring connection reset")
|
156
|
+
elif e.errno == errno.ENOTCONN:
|
157
|
+
self.log.debug("Ignoring socket not connected")
|
158
|
+
else:
|
159
|
+
self.log.debug("Ignoring EPIPE")
|
160
|
+
except BaseException as e:
|
161
|
+
self.handle_error(req, client, addr, e)
|
162
|
+
finally:
|
163
|
+
util.close(client)
|
164
|
+
|
165
|
+
def handle_request(
|
166
|
+
self, listener: socket.socket, req: Any, client: socket.socket, addr: Any
|
167
|
+
) -> None:
|
168
|
+
environ = {}
|
169
|
+
resp = None
|
170
|
+
try:
|
171
|
+
request_start = datetime.now()
|
172
|
+
resp, environ = wsgi.create(
|
173
|
+
req, client, addr, listener.getsockname(), self.cfg
|
174
|
+
)
|
175
|
+
# Force the connection closed until someone shows
|
176
|
+
# a buffering proxy that supports Keep-Alive to
|
177
|
+
# the backend.
|
178
|
+
resp.force_close()
|
179
|
+
self.nr += 1
|
180
|
+
if self.nr >= self.max_requests:
|
181
|
+
self.log.info("Autorestarting worker after current request.")
|
182
|
+
self.alive = False
|
183
|
+
respiter = self.wsgi(environ, resp.start_response)
|
184
|
+
try:
|
185
|
+
if isinstance(respiter, environ["wsgi.file_wrapper"]):
|
186
|
+
resp.write_file(respiter)
|
187
|
+
else:
|
188
|
+
for item in respiter:
|
189
|
+
resp.write(item)
|
190
|
+
resp.close()
|
191
|
+
finally:
|
192
|
+
request_time = datetime.now() - request_start
|
193
|
+
self.log.access(resp, req, environ, request_time)
|
194
|
+
if hasattr(respiter, "close"):
|
195
|
+
respiter.close()
|
196
|
+
except OSError:
|
197
|
+
# pass to next try-except level
|
198
|
+
util.reraise(*sys.exc_info())
|
199
|
+
except Exception:
|
200
|
+
if resp and resp.headers_sent:
|
201
|
+
# If the requests have already been sent, we should close the
|
202
|
+
# connection to indicate the error.
|
203
|
+
self.log.exception("Error handling request")
|
204
|
+
try:
|
205
|
+
client.shutdown(socket.SHUT_RDWR)
|
206
|
+
client.close()
|
207
|
+
except OSError:
|
208
|
+
pass
|
209
|
+
raise StopIteration()
|
210
|
+
raise
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of gunicorn released under the MIT license.
|
6
|
+
# See the LICENSE for more information.
|
7
|
+
#
|
8
|
+
# Vendored and modified for Plain.
|
9
|
+
import os
|
10
|
+
import platform
|
11
|
+
import tempfile
|
12
|
+
import time
|
13
|
+
from typing import TYPE_CHECKING
|
14
|
+
|
15
|
+
from .. import util
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from ..config import Config
|
19
|
+
|
20
|
+
PLATFORM = platform.system()
|
21
|
+
IS_CYGWIN = PLATFORM.startswith("CYGWIN")
|
22
|
+
|
23
|
+
|
24
|
+
class WorkerTmp:
|
25
|
+
def __init__(self, cfg: Config) -> None:
|
26
|
+
fd, name = tempfile.mkstemp(prefix="wplain-")
|
27
|
+
|
28
|
+
# unlink the file so we don't leak temporary files
|
29
|
+
try:
|
30
|
+
if not IS_CYGWIN:
|
31
|
+
util.unlink(name)
|
32
|
+
# In Python 3.8, open() emits RuntimeWarning if buffering=1 for binary mode.
|
33
|
+
# Because we never write to this file, pass 0 to switch buffering off.
|
34
|
+
self._tmp = os.fdopen(fd, "w+b", 0)
|
35
|
+
except Exception:
|
36
|
+
os.close(fd)
|
37
|
+
raise
|
38
|
+
|
39
|
+
def notify(self) -> None:
|
40
|
+
new_time = time.monotonic()
|
41
|
+
os.utime(self._tmp.fileno(), (new_time, new_time))
|
42
|
+
|
43
|
+
def last_update(self) -> float:
|
44
|
+
return os.fstat(self._tmp.fileno()).st_mtime
|
45
|
+
|
46
|
+
def fileno(self) -> int:
|
47
|
+
return self._tmp.fileno()
|
48
|
+
|
49
|
+
def close(self) -> None:
|
50
|
+
return self._tmp.close()
|
@@ -1,5 +1,5 @@
|
|
1
1
|
plain/AGENTS.md,sha256=As6EFSWWHJ9lYIxb2LMRqNRteH45SRs7a_VFslzF53M,1046
|
2
|
-
plain/CHANGELOG.md,sha256=
|
2
|
+
plain/CHANGELOG.md,sha256=pAk-j9vETAedPDTgTHXkh_v9wt-DCakHmVKdJzmd1zc,25908
|
3
3
|
plain/README.md,sha256=VvzhXNvf4I6ddmjBP9AExxxFXxs7RwyoxdgFm-W5dsg,4072
|
4
4
|
plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
|
5
5
|
plain/debug.py,sha256=C2OnFHtRGMrpCiHSt-P2r58JypgQZ62qzDBpV4mhtFM,855
|
@@ -24,7 +24,7 @@ plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
|
|
24
24
|
plain/cli/build.py,sha256=Jg5LMbmXHhCXZIYj_Gcjou3yGiEWw8wpbOGGsdk-wZw,3203
|
25
25
|
plain/cli/changelog.py,sha256=yCY887PT_D2viLz9f-uyu07Znqiv2-NEyCBqquNIukw,3590
|
26
26
|
plain/cli/chores.py,sha256=aaDVTpwBEmyQ4r_YeZ6U7Fw9UOIq5d7okwpq9HdfRbA,2521
|
27
|
-
plain/cli/core.py,sha256=
|
27
|
+
plain/cli/core.py,sha256=nL-a7zPtEBIa_XV-VOd9lqEUqmQAhlzsdHNwEmxNkWE,4285
|
28
28
|
plain/cli/docs.py,sha256=PU3v7Z7qgYFG-bClpuDg4JeWwC8uvLYX3ovkQDMseVs,1146
|
29
29
|
plain/cli/formatting.py,sha256=e1doTFalAM11bD_Cvqeu6sTap81JrQcB-4kMjZzAHmY,2737
|
30
30
|
plain/cli/install.py,sha256=mffSYBmSJSj44OPBfu53nBQoyoz4jk69DvppubIB0mU,2791
|
@@ -32,7 +32,9 @@ plain/cli/output.py,sha256=uZTHZR-Axeoi2r6fgcDCpDA7iQSRrktBtTf1yBT5djI,1426
|
|
32
32
|
plain/cli/preflight.py,sha256=5UXOowjiCMqsGIrpHg60f6Ptjk40rMiDSowN8wy5jSY,8541
|
33
33
|
plain/cli/print.py,sha256=7kv9ddXpwOHRSWp6FFLfX4wbmhV7neoOBlE0VcXWccw,238
|
34
34
|
plain/cli/registry.py,sha256=Z52nVE2bC2h_B_SENnXctn3mx3UWB0qYg969DVP7XX8,1106
|
35
|
+
plain/cli/runtime.py,sha256=YbGYfwkH0VxfuIMbOCwM9wSWiQKusPn_gVeGod8OFaE,743
|
35
36
|
plain/cli/scaffold.py,sha256=AMAVnTYySgR5nz4sVp3mn0gEGfTKE1N8ZlrVrg2SrFU,1364
|
37
|
+
plain/cli/server.py,sha256=qe4N-eCnFVuEP5ogIgzU9leurv8gi1riSplvin9TjAI,3297
|
36
38
|
plain/cli/settings.py,sha256=kafbcPzy8khdtzLRyOHRl215va3E7U_h5USOA39UA3k,2008
|
37
39
|
plain/cli/shell.py,sha256=urTp24D4UsKmYi9nT7OOdlT4WhXjkpFVrGYfNNVsXEE,1980
|
38
40
|
plain/cli/startup.py,sha256=1nxXQucDkBxXriEN4wI2tiwG96PBNFndVrOyfzvJFdI,1061
|
@@ -102,6 +104,30 @@ plain/runtime/__init__.py,sha256=dvF5ipRckVf6LQgY4kdxE_dTlCdncuawQf3o5EqLq9k,252
|
|
102
104
|
plain/runtime/global_settings.py,sha256=Q-bQP3tNnnuJZvfevGai639RIF_jhd7Dszt-DzTTz68,5509
|
103
105
|
plain/runtime/user_settings.py,sha256=Tbd0J6bxp18tKmFsQcdlxeFhUQU68PYtsswzQ2IcfNc,11467
|
104
106
|
plain/runtime/utils.py,sha256=sHOv9SWCalBtg32GtZofimM2XaQtf_jAyyf6RQuOlGc,851
|
107
|
+
plain/server/LICENSE,sha256=Xt_dw4qYwQI9qSi2u8yMZeb4HuMRp5tESRKhtvvJBgA,1707
|
108
|
+
plain/server/README.md,sha256=2dwgogY6rBDUA4dgDrrm7zIK66M_ZdULKNsyuE8UTRE,2731
|
109
|
+
plain/server/__init__.py,sha256=DtRgEcr4IxF4mrtCHloIprk_Q4k1oju4F2VHoyvu4ow,212
|
110
|
+
plain/server/app.py,sha256=ozaqdb-a_T3ps7T5EJwIPM63F_497J4o7kw9Pbq7Ga0,1229
|
111
|
+
plain/server/arbiter.py,sha256=qqTNgbrL0UzZ8sNUV0QP4Z_nzE4D4Y2smvHL33O-QcE,17403
|
112
|
+
plain/server/config.py,sha256=LSBi_E5JNmyzNY_XqAPYQ4-rDRw-NqxrMNyvQpFWbzQ,3335
|
113
|
+
plain/server/errors.py,sha256=sKl_OJ5Uw-a_r_dZ2o4I8JaKeTrjvY_LR12F6B_p4-g,956
|
114
|
+
plain/server/glogging.py,sha256=Ab49Btbr9UvGRgBk8KGVrzsJaFKN1uD0ZurmIC0GYFY,9692
|
115
|
+
plain/server/pidfile.py,sha256=8Fcl9u7gvUJjY5z01qGAeRsi13_jAM8CRdeyqL3h2i0,2538
|
116
|
+
plain/server/reloader.py,sha256=x3Oe1qmlprnkNRa-RQeuANdfmpRQvMSFfdTtBPIA5z0,4517
|
117
|
+
plain/server/sock.py,sha256=NFKtlrMstOT3xU2yKI6BLAzv_WE7VEGt3dHDkFPnPSo,6192
|
118
|
+
plain/server/util.py,sha256=_CjcbHr3ES9lP84s6ficFpZPxrVjntBYqHX4h5lQbCA,10472
|
119
|
+
plain/server/http/__init__.py,sha256=kQwTk1l3hYJwVrzr1p-XNAbWYe0icsD7l0ZyGRXMbOI,300
|
120
|
+
plain/server/http/body.py,sha256=cz18F4C_gm9h5XvsStV0ncanBjZO1-pegbmbmiMpsQA,8352
|
121
|
+
plain/server/http/errors.py,sha256=uqrzOzjjdqXfFim64pc58cxPlbsoxRNiLx0WzIJSzkw,3719
|
122
|
+
plain/server/http/message.py,sha256=5UdOA7CMqiXW_xy0c3d_rPHOvj5YQQHhbkx4Hf3Ttbc,15082
|
123
|
+
plain/server/http/parser.py,sha256=TLcqdRS9_008n8MpgLORmjomyZKLuKNzdKA8Cej8mII,1681
|
124
|
+
plain/server/http/unreader.py,sha256=jD2PGZ574FGmQOZlqWfs1VWzp-ttfIso_GzYaId6KYQ,2238
|
125
|
+
plain/server/http/wsgi.py,sha256=D-wVKdgKppDR_ZQd834yXtju60t1Jg5bFc5QEr7Iz38,13897
|
126
|
+
plain/server/workers/__init__.py,sha256=jRQzuvFZu3pMJqs4JgURZxhxlqBCNiO3do5uIuP8-JA,302
|
127
|
+
plain/server/workers/base.py,sha256=O0-s-WS0AjTVD8YHGoIGUZupGfYAi9joUkJpwsvMTY0,9761
|
128
|
+
plain/server/workers/gthread.py,sha256=6F_YfhrlPV3hmxwI8_-jgGq4pCmkQBKVO15Wrg-iQ7Y,13504
|
129
|
+
plain/server/workers/sync.py,sha256=I28Icl1aKNIOlVaM76UOqQvjtSetkG-tdc5eiAvAmYA,7272
|
130
|
+
plain/server/workers/workertmp.py,sha256=egGReVvldlOBQfQGcpLpjt0zvPwR4C_N-UJKG-U_6w4,1299
|
105
131
|
plain/signals/README.md,sha256=XefXqROlDhzw7Z5l_nx6Mhq6n9jjQ-ECGbH0vvhKWYg,272
|
106
132
|
plain/signals/__init__.py,sha256=eAs0kLqptuP6I31dWXeAqRNji3svplpAV4Ez6ktjwXM,131
|
107
133
|
plain/signals/dispatch/__init__.py,sha256=FzEygqV9HsM6gopio7O2Oh_X230nA4d5Q9s0sUjMq0E,292
|
@@ -162,8 +188,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
|
|
162
188
|
plain/views/objects.py,sha256=5y0PoPPo07dQTTcJ_9kZcx0iI1O7regsooAIK4VqXQ0,5579
|
163
189
|
plain/views/redirect.py,sha256=mIpSAFcaEyeLDyv4Fr6g_ektduG4Wfa6s6L-rkdazmM,2154
|
164
190
|
plain/views/templates.py,sha256=9LgDMCv4C7JzLiyw8jHF-i4350ukwgixC_9y4faEGu0,1885
|
165
|
-
plain-0.
|
166
|
-
plain-0.
|
167
|
-
plain-0.
|
168
|
-
plain-0.
|
169
|
-
plain-0.
|
191
|
+
plain-0.76.0.dist-info/METADATA,sha256=ypCMyEzKG6CwHD_QhK2NAYdWYeTNY_-9ZRaWe4RwB3o,4482
|
192
|
+
plain-0.76.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
193
|
+
plain-0.76.0.dist-info/entry_points.txt,sha256=wvMzY-iREvfqRgyLm77htPp4j_8CQslLoZA15_AnNo8,171
|
194
|
+
plain-0.76.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
195
|
+
plain-0.76.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|