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.
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.75.0
3
+ Version: 0.76.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,5 +1,5 @@
1
1
  plain/AGENTS.md,sha256=As6EFSWWHJ9lYIxb2LMRqNRteH45SRs7a_VFslzF53M,1046
2
- plain/CHANGELOG.md,sha256=PJbWcLkRQVfMtpmeZ_Ga6C4eo-0AkB8qIheC7vRRMgQ,23956
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=lUy6QljeqpdUdQWri5MASht8MG95IvlxMS4jySQGFGQ,3241
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.75.0.dist-info/METADATA,sha256=stPlpTtFhA_NaICFaJYV22Pa4Rd2uIbZjhtocI6KCkU,4482
166
- plain-0.75.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
167
- plain-0.75.0.dist-info/entry_points.txt,sha256=wvMzY-iREvfqRgyLm77htPp4j_8CQslLoZA15_AnNo8,171
168
- plain-0.75.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
169
- plain-0.75.0.dist-info/RECORD,,
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