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/IOLoop/Server.py ADDED
@@ -0,0 +1,458 @@
1
+ """
2
+ Pyjo.IOLoop.Server - Non-blocking TCP and UNIX domain socket server
3
+ ===================================================================
4
+ ::
5
+
6
+ from Pyjo.IOLoop.Server import Server
7
+
8
+ # Create listen socket
9
+ server = Server()
10
+ server.on('accept', lambda server, handle: ...)
11
+ server.listen(port=3000)
12
+
13
+ # Or import the module, as in Google Python Style Guide
14
+
15
+ import Pyjo.IOLoop.Server
16
+
17
+ # Create listen socket
18
+ server = Pyjo.IOLoop.Server.new()
19
+ server.on('accept', lambda server, handle: ...)
20
+ server.listen(port=3000)
21
+
22
+ # Start and stop accepting connections
23
+ server.start()
24
+ server.stop()
25
+
26
+ # Start reactor if necessary
27
+ if not server.reactor.is_running:
28
+ server.reactor.start()
29
+
30
+ :mod:`Pyjo.IOLoop.Server` accepts TCP/IP and UNIX domain socket connections
31
+ for :mod:`Pyjo.IOLoop`.
32
+
33
+ The end of a ``with`` block closes the listen socket.
34
+
35
+ Events
36
+ ------
37
+
38
+ :mod:`Pyjo.IOLoop.Server` inherits all events from :mod:`Pyjo.EventEmitter`
39
+ and can emit the following new ones.
40
+
41
+ accept
42
+ ~~~~~~
43
+ ::
44
+
45
+ server.on('accept', lambda server, handle: ...)
46
+
47
+ Emitted for each accepted connection, with its :class:`socket.socket`, or the
48
+ :class:`ssl.SSLSocket` once TLS has been negotiated.
49
+
50
+ See also
51
+ --------
52
+
53
+ :mod:`Pyjoyment`, https://mojolicious.org.
54
+
55
+ Ported from `Mojo::IOLoop::Server <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/IOLoop/Server.pm>`_
56
+ of Mojolicious 9.49.
57
+ """
58
+
59
+ import Pyjo.EventEmitter
60
+ import Pyjo.IOLoop
61
+ import Pyjo.IOLoop.TLS
62
+ import Pyjo.Reactor
63
+
64
+ import errno
65
+ import os
66
+ import re
67
+ import socket
68
+ import stat
69
+ import sys
70
+ import weakref
71
+
72
+ from types import TracebackType
73
+ from typing import Any, Self
74
+
75
+
76
+ class Pyjo_IOLoop_Server(Pyjo.EventEmitter.object):
77
+ """
78
+ :mod:`Pyjo.IOLoop.Server` inherits all methods from
79
+ :mod:`Pyjo.EventEmitter` and implements the following new ones.
80
+ """
81
+
82
+ def __init__(self) -> None:
83
+ """::
84
+
85
+ server = Pyjo.IOLoop.Server.new()
86
+
87
+ Construct a new :mod:`Pyjo.IOLoop.Server` object.
88
+ """
89
+ super().__init__()
90
+ self._active = False
91
+ self._args: dict[str, Any] = {}
92
+ self._handle: socket.socket | None = None
93
+ self._reactor: weakref.ref[Pyjo.Reactor.object] | None = None
94
+ self._reuse: str | None = None
95
+ self._watched: weakref.ref[Pyjo.Reactor.object] | None = None
96
+
97
+ def __del__(self) -> None:
98
+ # Module globals may be gone at the end of the interpreter
99
+ try:
100
+ if not sys.is_finalizing() and getattr(self, '_handle', None) is not None:
101
+ reactor = self._reactor() if self._reactor is not None else None
102
+ Pyjo.Reactor._finalize(self.close, reactor) # pyright: ignore[reportPrivateUsage]
103
+ except Exception:
104
+ pass
105
+
106
+ # Not in Mojolicious: a context manager, which closes the listen socket where the block ends, as PyPy frees the
107
+ # object only when its garbage collector runs
108
+ def __enter__(self) -> Self:
109
+ return self
110
+
111
+ def __exit__(
112
+ self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
113
+ ) -> None:
114
+ self.close()
115
+
116
+ @property
117
+ def reactor(self) -> Pyjo.Reactor.object:
118
+ """::
119
+
120
+ reactor = server.reactor
121
+ server.reactor = Pyjo.Reactor.Poll.new()
122
+
123
+ Low-level event reactor, defaults to the ``reactor`` attribute value of
124
+ the global :mod:`Pyjo.IOLoop` singleton. Note that this attribute is
125
+ weakened.
126
+ """
127
+ if self._reactor is None:
128
+ self._reactor = weakref.ref(Pyjo.IOLoop.singleton.reactor)
129
+ if (reactor := self._reactor()) is None:
130
+ raise ReferenceError('weakly-referenced object no longer exists')
131
+ return reactor
132
+
133
+ @reactor.setter
134
+ def reactor(self, value: Pyjo.Reactor.object) -> None:
135
+ self._reactor = weakref.ref(value)
136
+
137
+ # Not in Mojolicious: what DESTROY does, which Python does when it frees the server, PyPy only later
138
+ def close(self) -> None:
139
+ """::
140
+
141
+ server.close()
142
+
143
+ Stop accepting connections, close the listen socket and remove it from
144
+ the ``PYJO_REUSE`` environment variable, as it happens when the server
145
+ is freed.
146
+ """
147
+ if (reuse := self._reuse) is not None:
148
+ self._reuse = None
149
+ os.environ['PYJO_REUSE'] = re.sub(
150
+ rf'(?:^|,){re.escape(reuse)}', '', os.environ.get('PYJO_REUSE', ''), count=1
151
+ )
152
+ if (handle := self._handle) is not None:
153
+ if self._reactor is None or self._reactor() is not None:
154
+ self.stop()
155
+ self._handle = None
156
+ handle.close()
157
+
158
+ @classmethod
159
+ def generate_port(cls) -> int:
160
+ """::
161
+
162
+ port = Pyjo.IOLoop.Server.object.generate_port()
163
+
164
+ Find a free TCP port, primarily used for tests.
165
+ """
166
+ with socket.create_server(('127.0.0.1', 0), backlog=5) as handle:
167
+ return handle.getsockname()[1]
168
+
169
+ @property
170
+ def handle(self) -> socket.socket | None:
171
+ """::
172
+
173
+ handle = server.handle
174
+
175
+ Get handle for server, usually a :class:`socket.socket` object.
176
+ """
177
+ return self._handle
178
+
179
+ @property
180
+ def is_accepting(self) -> bool:
181
+ """::
182
+
183
+ boolean = server.is_accepting
184
+
185
+ Check if connections are currently being accepted.
186
+ """
187
+ return self._active
188
+
189
+ def listen(self, **options: Any) -> None:
190
+ """::
191
+
192
+ server.listen(port=3000)
193
+
194
+ Create a new listen socket.
195
+
196
+ These options are currently available:
197
+
198
+ address
199
+ ::
200
+
201
+ address='127.0.0.1'
202
+
203
+ Local address to listen on, defaults to ``0.0.0.0``.
204
+
205
+ backlog
206
+ ::
207
+
208
+ backlog=128
209
+
210
+ Maximum backlog size, defaults to :data:`socket.SOMAXCONN`.
211
+
212
+ fd
213
+ ::
214
+
215
+ fd=3
216
+
217
+ File descriptor with an already prepared listen socket.
218
+
219
+ path
220
+ ::
221
+
222
+ path='/tmp/myapp.sock'
223
+
224
+ Path for UNIX domain socket to listen on.
225
+
226
+ port
227
+ ::
228
+
229
+ port=80
230
+
231
+ Port to listen on, defaults to a random port.
232
+
233
+ reuse
234
+ ::
235
+
236
+ reuse=True
237
+
238
+ Allow multiple servers to use the same port with the
239
+ ``SO_REUSEPORT`` socket option, which Windows does not have.
240
+
241
+ single_accept
242
+ ::
243
+
244
+ single_accept=True
245
+
246
+ Only accept one connection at a time.
247
+
248
+ tls
249
+ ::
250
+
251
+ tls=True
252
+
253
+ Enable TLS.
254
+
255
+ tls_ca
256
+ ::
257
+
258
+ tls_ca='/etc/tls/ca.crt'
259
+
260
+ Path to TLS certificate authority file.
261
+
262
+ tls_cert
263
+ ::
264
+
265
+ tls_cert='/etc/tls/server.crt'
266
+ tls_cert={'mojolicious.org': '/etc/tls/mojo.crt'}
267
+
268
+ Path to the TLS cert file, defaults to a built-in test certificate.
269
+
270
+ tls_context
271
+ ::
272
+
273
+ tls_context=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
274
+
275
+ The :class:`ssl.SSLContext` to use instead of the one that the
276
+ other options make.
277
+
278
+ tls_key
279
+ ::
280
+
281
+ tls_key='/etc/tls/server.key'
282
+ tls_key={'mojolicious.org': '/etc/tls/mojo.key'}
283
+
284
+ Path to the TLS key file, defaults to a built-in test key.
285
+ """
286
+ # Look for reusable file descriptor
287
+ path = options.get('path')
288
+ address = options.get('address') or '0.0.0.0'
289
+ port = options.get('port')
290
+ reuse_env = os.environ.setdefault('PYJO_REUSE', '')
291
+ fd: int | None = None
292
+ if path and (m := re.search(rf'(?:^|,)unix:{re.escape(path)}:(\d+)', reuse_env)):
293
+ fd = int(m[1])
294
+ elif port and (m := re.search(rf'(?:^|,){re.escape(f"{address}:{port}")}:(\d+)', reuse_env)):
295
+ fd = int(m[1])
296
+
297
+ # Reuse file descriptor
298
+ if fd is None:
299
+ fd = options.get('fd')
300
+ if fd is not None:
301
+ try:
302
+ handle = socket.socket(fileno=fd)
303
+ except OSError as e:
304
+ raise _error(e, f"Can't open file descriptor {fd}") from e
305
+
306
+ else:
307
+ backlog = options.get('backlog')
308
+ backlog = socket.SOMAXCONN if backlog is None else int(backlog)
309
+ created: socket.socket | None = None
310
+ try:
311
+ # UNIX domain socket
312
+ if path:
313
+ # Not in Mojolicious: Python has no UNIX domain sockets on Windows
314
+ if sys.platform == 'win32':
315
+ raise OSError(errno.EAFNOSUPPORT, 'UNIX domain sockets are not supported')
316
+ if os.path.exists(path) and stat.S_ISSOCK(os.stat(path).st_mode):
317
+ os.remove(path)
318
+ handle = created = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
319
+ handle.bind(path)
320
+ handle.listen(backlog)
321
+ reuse = self._reuse = f'unix:{path}:{handle.fileno()}'
322
+
323
+ # IP socket
324
+ else:
325
+ host = address.replace('[', '').replace(']', '')
326
+ family, _, _, _, sockaddr = socket.getaddrinfo(
327
+ host, port or 0, type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE
328
+ )[0]
329
+ handle = created = socket.socket(family, socket.SOCK_STREAM)
330
+ # Unlike Mojolicious: no SO_REUSEADDR on Windows, where it lets a second socket listen on a port
331
+ # that is taken, as socket.create_server leaves it out there, and SO_REUSEPORT is not there at all,
332
+ # where Perl dies of the missing constant
333
+ if sys.platform != 'win32':
334
+ handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
335
+ if options.get('reuse'):
336
+ if sys.platform == 'win32':
337
+ raise OSError(errno.ENOPROTOOPT, 'SO_REUSEPORT is not supported')
338
+ handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
339
+ handle.bind(sockaddr)
340
+ handle.listen(backlog)
341
+ reuse = self._reuse = f'{address}:{handle.getsockname()[1]}:{handle.fileno()}'
342
+ except OSError as e:
343
+ # The socket that cannot listen is closed, as Perl frees it
344
+ if created is not None:
345
+ created.close()
346
+ raise _error(e, "Can't create listen socket") from e
347
+
348
+ os.environ['PYJO_REUSE'] = f'{reuse_env},{reuse}' if len(reuse_env) else reuse
349
+
350
+ # Allow file descriptor inheritance, as local $^F = 1023
351
+ handle.set_inheritable(True)
352
+ handle.setblocking(False)
353
+ self._args, self._handle = options, handle
354
+
355
+ if not Pyjo.IOLoop.TLS.object.can_tls() and options.get('tls'):
356
+ raise RuntimeError('TLS support is disabled')
357
+
358
+ @property
359
+ def port(self) -> int | None:
360
+ """::
361
+
362
+ port = server.port
363
+
364
+ Get port this server is listening on.
365
+ """
366
+ if self._handle is None or self._handle.family == getattr(socket, 'AF_UNIX', None):
367
+ return None
368
+ return self._handle.getsockname()[1]
369
+
370
+ def start(self) -> None:
371
+ """::
372
+
373
+ server.start()
374
+
375
+ Start or resume accepting connections.
376
+ """
377
+ if (handle := self._handle) is None:
378
+ return
379
+ # Not in Mojolicious: an acceptor that is active already in this reactor is not watched again, where Mojo
380
+ # replaces its watcher
381
+ reactor = self.reactor
382
+ if self._active and self._watched is not None and self._watched() is reactor:
383
+ return
384
+ ref = weakref.ref(self)
385
+
386
+ def cb(reactor: Any, writable: bool) -> None:
387
+ if (server := ref()) is not None:
388
+ server._accept()
389
+
390
+ self._active, self._watched = True, weakref.ref(reactor)
391
+ reactor.io(handle, cb).watch(handle, True, False)
392
+
393
+ def stop(self) -> None:
394
+ """::
395
+
396
+ server.stop()
397
+
398
+ Stop accepting connections.
399
+ """
400
+ if self._active:
401
+ self._active, self._watched = False, None
402
+ if self._handle is not None:
403
+ self.reactor.remove(self._handle)
404
+
405
+ def _accept(self) -> None:
406
+ # Greedy accept
407
+ args = self._args
408
+ accepted = 0
409
+ ref = weakref.ref(self)
410
+
411
+ def upgrade(tls: Any, handle: Any) -> None:
412
+ if (server := ref()) is not None:
413
+ server.emit('accept', handle)
414
+
415
+ while self._active and self._handle is not None:
416
+ if args.get('single_accept'):
417
+ if accepted:
418
+ break
419
+ accepted += 1
420
+ try:
421
+ handle = self._handle.accept()[0]
422
+ except OSError:
423
+ return
424
+ handle.setblocking(False)
425
+
426
+ # Disable Nagle's algorithm
427
+ try:
428
+ handle.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
429
+ except OSError:
430
+ pass
431
+
432
+ if not args.get('tls'):
433
+ self.emit('accept', handle)
434
+ continue
435
+
436
+ # Start TLS handshake
437
+ tls = Pyjo.IOLoop.TLS.new(handle)
438
+ tls.reactor = self.reactor
439
+ tls.on('upgrade', upgrade)
440
+ tls.on('error', _ignore)
441
+ tls.negotiate(**{**args, 'server': True})
442
+
443
+
444
+ # The errors of a TLS handshake that fails are ignored
445
+ def _ignore(tls: Any, err: Any) -> None:
446
+ pass
447
+
448
+
449
+ # Mojo's croak with $!: the same kind of error, with the message in front
450
+ def _error(e: OSError, message: str) -> OSError:
451
+ error = type(e)(f'{message}: {e.strerror}' if e.strerror else message)
452
+ error.errno = e.errno
453
+ return error
454
+
455
+
456
+ new = Pyjo_IOLoop_Server.new
457
+ object = Pyjo_IOLoop_Server
458
+ Server = Pyjo_IOLoop_Server