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/TLS.py ADDED
@@ -0,0 +1,360 @@
1
+ """
2
+ Pyjo.IOLoop.TLS - Non-blocking TLS handshake
3
+ ============================================
4
+ ::
5
+
6
+ import Pyjo.IOLoop.TLS
7
+
8
+ # Negotiate TLS
9
+ tls = Pyjo.IOLoop.TLS.new(old_handle)
10
+ tls.on('upgrade', lambda tls, new_handle: ...)
11
+ tls.on('error', lambda tls, err: ...)
12
+ tls.negotiate(server=True)
13
+
14
+ # Start reactor if necessary
15
+ if not tls.reactor.is_running:
16
+ tls.reactor.start()
17
+
18
+ :mod:`Pyjo.IOLoop.TLS` negotiates TLS for :mod:`Pyjo.IOLoop`, with the
19
+ :mod:`ssl` module of the standard library.
20
+
21
+ The end of a ``with`` block closes the connection of the handshake.
22
+
23
+ Events
24
+ ------
25
+
26
+ :mod:`Pyjo.IOLoop.TLS` inherits all events from :mod:`Pyjo.EventEmitter` and
27
+ can emit the following new ones.
28
+
29
+ upgrade
30
+ ~~~~~~~
31
+ ::
32
+
33
+ tls.on('upgrade', lambda tls, handle: ...)
34
+
35
+ Emitted once TLS has been negotiated, with the :class:`ssl.SSLSocket`.
36
+
37
+ error
38
+ ~~~~~
39
+ ::
40
+
41
+ tls.on('error', lambda tls, err: ...)
42
+
43
+ Emitted if an error occurs during negotiation, fatal if unhandled.
44
+
45
+ See also
46
+ --------
47
+
48
+ :mod:`Pyjoyment`, https://mojolicious.org.
49
+
50
+ Ported from `Mojo::IOLoop::TLS <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/IOLoop/TLS.pm>`_
51
+ of Mojolicious 9.49.
52
+ """
53
+
54
+ import Pyjo.EventEmitter
55
+ import Pyjo.IOLoop
56
+ import Pyjo.Reactor
57
+
58
+ import os
59
+ import ssl
60
+ import sys
61
+ import weakref
62
+
63
+ from types import TracebackType
64
+ from typing import Any, Self, cast
65
+
66
+
67
+ TLS = not os.environ.get('PYJO_NO_TLS')
68
+
69
+ # The built-in certificate for localhost and 127.0.0.1, which certs/ca.crt signs
70
+ CERT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'certs', 'server.crt')
71
+ KEY = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'certs', 'server.key')
72
+
73
+
74
+ class Pyjo_IOLoop_TLS(Pyjo.EventEmitter.object):
75
+ """
76
+ :mod:`Pyjo.IOLoop.TLS` inherits all methods from :mod:`Pyjo.EventEmitter`
77
+ and implements the following new ones.
78
+ """
79
+
80
+ def __init__(self, handle: Any = None) -> None:
81
+ """::
82
+
83
+ tls = Pyjo.IOLoop.TLS.new(handle)
84
+
85
+ Construct a new :mod:`Pyjo.IOLoop.TLS` object.
86
+ """
87
+ super().__init__()
88
+ self._handle: Any = handle
89
+ self._reactor: weakref.ref[Pyjo.Reactor.object] | None = None
90
+
91
+ def __del__(self) -> None:
92
+ # Module globals may be gone at the end of the interpreter
93
+ try:
94
+ if not sys.is_finalizing() and getattr(self, '_handle', None) is not None:
95
+ reactor = self._reactor() if self._reactor is not None else None
96
+ Pyjo.Reactor._finalize(self._release, reactor) # pyright: ignore[reportPrivateUsage]
97
+ except Exception:
98
+ pass
99
+
100
+ # Not in Mojolicious: a context manager, which closes the connection of the handshake where the block ends, as PyPy
101
+ # frees the object only when its garbage collector runs
102
+ def __enter__(self) -> Self:
103
+ return self
104
+
105
+ def __exit__(
106
+ self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
107
+ ) -> None:
108
+ self.close()
109
+
110
+ def _release(self) -> None:
111
+ self._cleanup()
112
+ self._close()
113
+
114
+ @property
115
+ def reactor(self) -> Pyjo.Reactor.object:
116
+ """::
117
+
118
+ reactor = tls.reactor
119
+ tls.reactor = Pyjo.Reactor.Poll.new()
120
+
121
+ Low-level event reactor, defaults to the ``reactor`` attribute value of
122
+ the global :mod:`Pyjo.IOLoop` singleton. Note that this attribute is
123
+ weakened.
124
+ """
125
+ if self._reactor is None:
126
+ self._reactor = weakref.ref(Pyjo.IOLoop.singleton.reactor)
127
+ if (reactor := self._reactor()) is None:
128
+ raise ReferenceError('weakly-referenced object no longer exists')
129
+ return reactor
130
+
131
+ @reactor.setter
132
+ def reactor(self, value: Pyjo.Reactor.object) -> None:
133
+ self._reactor = weakref.ref(value)
134
+
135
+ @classmethod
136
+ def can_tls(cls) -> bool:
137
+ """::
138
+
139
+ boolean = Pyjo.IOLoop.TLS.object.can_tls()
140
+
141
+ True if TLS support is enabled, as it is unless the ``PYJO_NO_TLS``
142
+ environment variable is set.
143
+ """
144
+ return TLS
145
+
146
+ # Not in Mojolicious: the release of the connection of the handshake, which Perl does in DESTROY, also at the end of
147
+ # a with block
148
+ def close(self) -> None:
149
+ """::
150
+
151
+ tls.close()
152
+
153
+ Close the connection of the handshake, as the end of a ``with`` block
154
+ does.
155
+ """
156
+ self._release()
157
+
158
+ def negotiate(self, **options: Any) -> None:
159
+ """::
160
+
161
+ tls.negotiate(server=True)
162
+ tls.negotiate(tls_ca='/etc/tls/ca.crt')
163
+
164
+ Negotiate TLS.
165
+
166
+ These options are currently available:
167
+
168
+ address
169
+ ::
170
+
171
+ address='mojolicious.org'
172
+
173
+ Name of the server, for SNI and the verification of its
174
+ certificate on the client-side.
175
+
176
+ server
177
+ ::
178
+
179
+ server=True
180
+
181
+ Negotiate TLS from the server-side, defaults to the client-side.
182
+
183
+ tls_ca
184
+ ::
185
+
186
+ tls_ca='/etc/tls/ca.crt'
187
+
188
+ Path to TLS certificate authority file. On the server-side, a
189
+ client certificate is then required.
190
+
191
+ tls_cert
192
+ ::
193
+
194
+ tls_cert='/etc/tls/server.crt'
195
+ tls_cert={'mojolicious.org': '/etc/tls/mojo.crt'}
196
+
197
+ Path to the TLS cert file, defaults to a built-in test certificate
198
+ on the server-side.
199
+
200
+ tls_context
201
+ ::
202
+
203
+ tls_context=ssl.create_default_context()
204
+
205
+ The :class:`ssl.SSLContext` to use instead of the one that the
206
+ other options make.
207
+
208
+ tls_hostname
209
+ ::
210
+
211
+ tls_hostname='mojolicious.org'
212
+ tls_hostname=''
213
+
214
+ Name of the server for SNI and the verification of its
215
+ certificate on the client-side instead of ``address``, an empty
216
+ one for no SNI.
217
+
218
+ tls_key
219
+ ::
220
+
221
+ tls_key='/etc/tls/server.key'
222
+ tls_key={'mojolicious.org': '/etc/tls/mojo.key'}
223
+
224
+ Path to the TLS key file, defaults to a built-in test key on the
225
+ server-side.
226
+ """
227
+ if not TLS:
228
+ return self._error('TLS support is disabled')
229
+
230
+ handle = self._handle
231
+ server = bool(options.get('server'))
232
+ try:
233
+ context = _expand(options)
234
+ # Not in Mojolicious: tls_hostname for the SSL_hostname and SSL_verifycn_name of IO::Socket::SSL
235
+ hostname = None if server else options.get('tls_hostname', options.get('address')) or None
236
+ handle = context.wrap_socket(
237
+ handle, server_side=server, do_handshake_on_connect=False, server_hostname=hostname
238
+ )
239
+ except (OSError, ValueError) as e:
240
+ return self._error(e)
241
+ self._handle = handle
242
+
243
+ # The reactor keeps the negotiation alive until it is done, as Mojo's callback refers to it
244
+ def cb(reactor: Any, writable: bool) -> None:
245
+ self._tls(handle)
246
+
247
+ self.reactor.io(handle, cb)
248
+
249
+ def _cleanup(self) -> None:
250
+ reactor = self._reactor() if self._reactor is not None else None
251
+ if reactor is not None and self._handle is not None:
252
+ try:
253
+ reactor.remove(self._handle)
254
+ except Exception:
255
+ pass
256
+
257
+ # The handle that is not upgraded is closed where it is dropped, as Perl closes it with the last reference
258
+ def _close(self) -> None:
259
+ if (handle := self._handle) is not None:
260
+ self._handle = None
261
+ handle.close()
262
+
263
+ def _error(self, err: Any) -> None:
264
+ self._cleanup()
265
+ self._close()
266
+ self.emit('error', err)
267
+
268
+ def _tls(self, handle: ssl.SSLSocket) -> None:
269
+ # Switch between reading and writing
270
+ try:
271
+ handle.do_handshake()
272
+ except ssl.SSLWantReadError:
273
+ self.reactor.watch(handle, True, False)
274
+ return
275
+ except ssl.SSLWantWriteError:
276
+ self.reactor.watch(handle, True, True)
277
+ return
278
+ except OSError as e:
279
+ return self._error(e)
280
+
281
+ self._cleanup()
282
+ self._handle = None
283
+ self.emit('upgrade', handle)
284
+
285
+
286
+ # The certificates or keys of names, with '' for the one of any name
287
+ def _named(value: Any) -> dict[str, str]:
288
+ if isinstance(value, dict):
289
+ return {str(k): str(v) for k, v in cast(dict[Any, Any], value).items()}
290
+ return {'': value} if value else {}
291
+
292
+
293
+ # Left out from Mojolicious: _expand makes the options of IO::Socket::SSL, here an ssl.SSLContext
294
+ def _expand(options: dict[str, Any]) -> ssl.SSLContext:
295
+ # Unlike Mojolicious: tls_options of IO::Socket::SSL is tls_context, an ssl.SSLContext
296
+ if (context := options.get('tls_context')) is not None:
297
+ return context
298
+
299
+ ca = options.get('tls_ca')
300
+ if ca is not None and not os.path.isfile(ca):
301
+ ca = None
302
+ cert, key = options.get('tls_cert'), options.get('tls_key')
303
+
304
+ if options.get('server'):
305
+
306
+ def server_context(cert: str, key: str) -> ssl.SSLContext:
307
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
308
+ # Not in Mojolicious: no session tickets of TLS 1.3 on Windows, which the server sends after the handshake
309
+ # of the client, where a write to a client that has closed already aborts the handshake of the server
310
+ if sys.platform == 'win32':
311
+ context.num_tickets = 0
312
+ context.load_cert_chain(cert, key)
313
+ if ca is not None:
314
+ context.load_verify_locations(ca)
315
+ context.verify_mode = ssl.CERT_REQUIRED
316
+ return context
317
+
318
+ certs, keys = _named(cert), _named(key)
319
+ context = server_context(certs.pop('', None) or CERT, keys.pop('', None) or KEY)
320
+
321
+ # Not in Mojolicious: a certificate for each name, which IO::Socket::SSL chooses by SNI
322
+ if len(certs):
323
+ contexts = {name: server_context(c, keys.get(name) or KEY) for name, c in certs.items()}
324
+
325
+ def sni(socket: ssl.SSLObject | ssl.SSLSocket, name: str | None, context: ssl.SSLContext) -> None:
326
+ if name is not None and (named := contexts.get(name)) is not None:
327
+ socket.context = named
328
+
329
+ context.sni_callback = sni
330
+ return context
331
+
332
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
333
+ if ca is not None:
334
+ context.load_verify_locations(ca)
335
+ else:
336
+ context.load_default_certs()
337
+ if cert:
338
+ context.load_cert_chain(cert, key)
339
+ if not options.get('tls_hostname', options.get('address')):
340
+ context.check_hostname = False
341
+ return context
342
+
343
+
344
+ # Not in Mojolicious: the context that negotiate makes of the options, for a server that changes it, as
345
+ # Mojo::Server::Daemon adds tls_options of IO::Socket::SSL to the other options
346
+ def context(**options: Any) -> ssl.SSLContext:
347
+ """::
348
+
349
+ context = context(server=True, tls_cert='/etc/tls/server.crt', tls_key='/etc/tls/server.key')
350
+
351
+ The :class:`ssl.SSLContext` that :meth:`Pyjo_IOLoop_TLS.negotiate` makes
352
+ of its options. A context of a server sends no session tickets of TLS 1.3
353
+ on Windows, where a client that closes the connection right after its
354
+ handshake would make the handshake of the server fail.
355
+ """
356
+ return _expand(options)
357
+
358
+
359
+ new = Pyjo_IOLoop_TLS.new
360
+ object = Pyjo_IOLoop_TLS