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
@@ -0,0 +1,476 @@
1
+ r"""
2
+ Pyjo.Message.Response - HTTP response
3
+ =====================================
4
+ ::
5
+
6
+ from Pyjo.Message.Response import Response
7
+
8
+ # Parse
9
+ res = Response()
10
+ res.parse(b'HTTP/1.0 200 OK\x0d\x0a')
11
+ res.parse(b'Content-Length: 12\x0d\x0a')
12
+ res.parse(b'Content-Type: text/plain\x0d\x0a\x0d\x0a')
13
+ res.parse(b'Hello World!')
14
+ print(res.code)
15
+ print(res.headers.content_type)
16
+ print(res.body)
17
+
18
+ # Or import the module, as in Google Python Style Guide
19
+
20
+ import Pyjo.Message.Response
21
+
22
+ # Parse
23
+ res = Pyjo.Message.Response.new()
24
+ res.parse(b'HTTP/1.0 200 OK\x0d\x0a')
25
+ res.parse(b'Content-Length: 12\x0d\x0a')
26
+ res.parse(b'Content-Type: text/plain\x0d\x0a\x0d\x0a')
27
+ res.parse(b'Hello World!')
28
+ print(res.code)
29
+ print(res.headers.content_type)
30
+ print(res.body)
31
+
32
+ # Build
33
+ res = Pyjo.Message.Response.new()
34
+ res.code = 200
35
+ res.headers.content_type = 'text/plain'
36
+ res.body = b'Hello World!'
37
+ print(res.to_bytes())
38
+
39
+ :mod:`Pyjo.Message.Response` is a container for HTTP responses, based on
40
+ `RFC 7230 <https://tools.ietf.org/html/rfc7230>`_ and
41
+ `RFC 7231 <https://tools.ietf.org/html/rfc7231>`_.
42
+
43
+ Events
44
+ ------
45
+
46
+ :mod:`Pyjo.Message.Response` inherits all events from :mod:`Pyjo.Message`.
47
+
48
+ See also
49
+ --------
50
+
51
+ :mod:`Pyjoyment`, https://mojolicious.org.
52
+
53
+ Ported from `Mojo::Message::Response <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Message/Response.pm>`_
54
+ of Mojolicious 9.49.
55
+ """
56
+
57
+ import Pyjo.Cookie
58
+ import Pyjo.Cookie.Response
59
+ import Pyjo.Date
60
+ import Pyjo.Message
61
+
62
+ import builtins
63
+ import http.client
64
+ import io
65
+ import re
66
+ import urllib.response
67
+
68
+ from collections.abc import Mapping
69
+ from typing import Any, Self, cast, overload
70
+
71
+
72
+ type Anything = builtins.object
73
+
74
+ # Unmarked codes are from RFC 7231
75
+ MESSAGES = {
76
+ 100: 'Continue',
77
+ 101: 'Switching Protocols',
78
+ 102: 'Processing', # RFC 2518 (WebDAV)
79
+ 103: 'Early Hints', # RFC 8297
80
+ 200: 'OK',
81
+ 201: 'Created',
82
+ 202: 'Accepted',
83
+ 203: 'Non-Authoritative Information',
84
+ 204: 'No Content',
85
+ 205: 'Reset Content',
86
+ 206: 'Partial Content',
87
+ 207: 'Multi-Status', # RFC 2518 (WebDAV)
88
+ 208: 'Already Reported', # RFC 5842
89
+ 226: 'IM Used', # RFC 3229
90
+ 300: 'Multiple Choices',
91
+ 301: 'Moved Permanently',
92
+ 302: 'Found',
93
+ 303: 'See Other',
94
+ 304: 'Not Modified',
95
+ 305: 'Use Proxy',
96
+ 307: 'Temporary Redirect',
97
+ 308: 'Permanent Redirect', # RFC 7538
98
+ 400: 'Bad Request',
99
+ 401: 'Unauthorized',
100
+ 402: 'Payment Required',
101
+ 403: 'Forbidden',
102
+ 404: 'Not Found',
103
+ 405: 'Method Not Allowed',
104
+ 406: 'Not Acceptable',
105
+ 407: 'Proxy Authentication Required',
106
+ 408: 'Request Timeout',
107
+ 409: 'Conflict',
108
+ 410: 'Gone',
109
+ 411: 'Length Required',
110
+ 412: 'Precondition Failed',
111
+ 413: 'Request Entity Too Large',
112
+ 414: 'Request-URI Too Long',
113
+ 415: 'Unsupported Media Type',
114
+ 416: 'Request Range Not Satisfiable',
115
+ 417: 'Expectation Failed',
116
+ 418: "I'm a teapot", # RFC 2324 :)
117
+ 421: 'Misdirected Request', # RFC 7540
118
+ 422: 'Unprocessable Entity', # RFC 2518 (WebDAV)
119
+ 423: 'Locked', # RFC 2518 (WebDAV)
120
+ 424: 'Failed Dependency', # RFC 2518 (WebDAV)
121
+ 425: 'Too Early', # RFC 8470
122
+ 426: 'Upgrade Required', # RFC 2817
123
+ 428: 'Precondition Required', # RFC 6585
124
+ 429: 'Too Many Requests', # RFC 6585
125
+ 431: 'Request Header Fields Too Large', # RFC 6585
126
+ 451: 'Unavailable For Legal Reasons', # RFC 7725
127
+ 500: 'Internal Server Error',
128
+ 501: 'Not Implemented',
129
+ 502: 'Bad Gateway',
130
+ 503: 'Service Unavailable',
131
+ 504: 'Gateway Timeout',
132
+ 505: 'HTTP Version Not Supported',
133
+ 506: 'Variant Also Negotiates', # RFC 2295
134
+ 507: 'Insufficient Storage', # RFC 2518 (WebDAV)
135
+ 508: 'Loop Detected', # RFC 5842
136
+ 509: 'Bandwidth Limit Exceeded', # Unofficial
137
+ 510: 'Not Extended', # RFC 2774
138
+ 511: 'Network Authentication Required', # RFC 6585
139
+ }
140
+
141
+
142
+ class Pyjo_Message_Response(Pyjo.Message.object):
143
+ """
144
+ :mod:`Pyjo.Message.Response` inherits all methods from :mod:`Pyjo.Message`
145
+ and implements the following new ones.
146
+ """
147
+
148
+ # The default of max_message_size, which a response raises
149
+ _MAX_MESSAGE_SIZE = 2147483648
150
+
151
+ def __init__(
152
+ self,
153
+ response: http.client.HTTPResponse | urllib.response.addinfourl | None = None,
154
+ /,
155
+ *,
156
+ code: int | None = None,
157
+ message: str | None = None,
158
+ **kwargs: Any,
159
+ ) -> None:
160
+ """::
161
+
162
+ res = Pyjo.Message.Response.new()
163
+ res = Pyjo.Message.Response.new(code=200, message='OK')
164
+ res = Pyjo.Message.Response.new(urllib.request.urlopen('http://127.0.0.1/'))
165
+
166
+ Construct a new :mod:`Pyjo.Message.Response` object, from a response
167
+ of urllib if it is given: an :class:`http.client.HTTPResponse`, as
168
+ :func:`urllib.request.urlopen` returns for HTTP, or a
169
+ :class:`urllib.response.addinfourl`, as it returns for other URLs and
170
+ :class:`urllib.error.HTTPError` is: its status, ``200`` for a response
171
+ without one, as of a ``file:`` or ``data:`` URL, its headers, and the
172
+ body without the transfer encoding, as :mod:`http.client` decodes it.
173
+ It reads the response and closes it.
174
+ """
175
+ super().__init__(**kwargs)
176
+
177
+ self.code = code
178
+ """::
179
+
180
+ code = res.code
181
+ res.code = 200
182
+
183
+ HTTP response status code.
184
+ """
185
+
186
+ self.message = message
187
+ """::
188
+
189
+ message = res.message
190
+ res.message = 'OK'
191
+
192
+ HTTP response status message.
193
+ """
194
+
195
+ self._start_buffer: bytes | None = None
196
+
197
+ # Not in Mojolicious: a response of urllib
198
+ if response is not None:
199
+ self._from_urllib(response)
200
+
201
+ @overload
202
+ def cookies(self) -> list[Pyjo.Cookie.Response.object]: ...
203
+
204
+ @overload
205
+ def cookies(self, cookie: Pyjo.Cookie.object | Mapping[str, Any], /, *cookies: Anything) -> Self: ...
206
+
207
+ def cookies(self, *cookies: Anything) -> list[Pyjo.Cookie.Response.object] | Self:
208
+ """::
209
+
210
+ cookies = res.cookies()
211
+ res = res.cookies(Pyjo.Cookie.Response.new())
212
+ res = res.cookies({'name': 'foo', 'value': 'bar'})
213
+
214
+ Access response cookies, usually :mod:`Pyjo.Cookie.Response` objects. ::
215
+
216
+ # Names of all cookies
217
+ for cookie in res.cookies():
218
+ print(cookie.name)
219
+ """
220
+ # Parse cookies
221
+ headers = self.headers
222
+ if not cookies:
223
+ return Pyjo.Cookie.Response.parse(headers.set_cookie)
224
+
225
+ # Add cookies
226
+ for cookie in cookies:
227
+ if isinstance(cookie, Mapping):
228
+ cookie = Pyjo.Cookie.Response.new(**cast(Mapping[str, Any], cookie))
229
+ headers.add('Set-Cookie', str(cookie))
230
+
231
+ return self
232
+
233
+ def default_message(self, code: int | None = None) -> str:
234
+ """::
235
+
236
+ message = res.default_message()
237
+ message = res.default_message(418)
238
+
239
+ Generate default response message for status code, defaults to using
240
+ :attr:`code`.
241
+ """
242
+ code = code or self.code
243
+ return MESSAGES.get(code if code is not None else 404, '')
244
+
245
+ @property
246
+ def max_message_size(self) -> int:
247
+ """::
248
+
249
+ size = res.max_message_size
250
+ res.max_message_size = 1024
251
+
252
+ Maximum message size in bytes, defaults to the value of the
253
+ ``PYJO_MAX_MESSAGE_SIZE`` environment variable or ``2147483648``
254
+ (2GiB). Setting the value to ``0`` will allow messages of indefinite
255
+ size.
256
+ """
257
+ return super().max_message_size
258
+
259
+ @max_message_size.setter
260
+ def max_message_size(self, value: int) -> None:
261
+ self._max_message_size = value
262
+
263
+ def extract_start_line(self, buffer: bytearray) -> bool | None:
264
+ """::
265
+
266
+ boolean = res.extract_start_line(buffer)
267
+
268
+ Extract status-line from a :class:`bytearray`, which loses it.
269
+ """
270
+ # We have a full response line
271
+ if not (m := re.match(rb'(.*?)\x0d?\x0a', bytes(buffer))):
272
+ return None
273
+ del buffer[: m.end()]
274
+ if not (m := re.fullmatch(rb'\s*HTTP/(\d\.\d)\s+(\d\d\d)\s*(.+)?', m[1])):
275
+ self.error = {'message': 'Bad response start-line'}
276
+ return False
277
+
278
+ content = self.content
279
+ self.code = int(m[2])
280
+ if self.is_empty:
281
+ content.skip_body = True
282
+ if content.auto_decompress is None:
283
+ content.auto_decompress = True
284
+ if content.auto_relax is None:
285
+ content.auto_relax = True
286
+ self.version = m[1].decode('latin-1')
287
+ self.message = m[3].decode('latin-1') if m[3] is not None else None
288
+ return True
289
+
290
+ def fix_headers(self) -> Self:
291
+ """::
292
+
293
+ res = res.fix_headers()
294
+
295
+ Make sure response has all required headers.
296
+ """
297
+ if self._fix:
298
+ return self
299
+ super().fix_headers()
300
+
301
+ # Date
302
+ headers = self.headers
303
+ if not headers.date:
304
+ headers.date = Pyjo.Date.new().to_str()
305
+
306
+ # RFC 7230 3.3.2
307
+ if self.is_empty:
308
+ headers.remove('Content-Length')
309
+
310
+ return self
311
+
312
+ def get_start_line_chunk(self, offset: int) -> bytes:
313
+ """::
314
+
315
+ data = res.get_start_line_chunk(offset)
316
+
317
+ Get a chunk of status-line data starting from a specific position.
318
+ Note that this method finalizes the response.
319
+ """
320
+ buffer = self._start_line()
321
+ self.emit('progress', 'start_line', offset)
322
+ return buffer[offset : offset + 131072]
323
+
324
+ @property
325
+ def is_client_error(self) -> bool:
326
+ """::
327
+
328
+ boolean = res.is_client_error
329
+
330
+ Check if this response has a ``4xx`` status :attr:`code`.
331
+ """
332
+ return self._status_class(400)
333
+
334
+ @property
335
+ def is_empty(self) -> bool:
336
+ """::
337
+
338
+ boolean = res.is_empty
339
+
340
+ Check if this response has a ``1xx``, ``204`` or ``304`` status
341
+ :attr:`code`.
342
+ """
343
+ if not (code := self.code):
344
+ return False
345
+ return self.is_info or code == 204 or code == 304
346
+
347
+ @property
348
+ def is_error(self) -> bool:
349
+ """::
350
+
351
+ boolean = res.is_error
352
+
353
+ Check if this response has a ``4xx`` or ``5xx`` status :attr:`code`.
354
+ """
355
+ return self._status_class(400, 500)
356
+
357
+ @property
358
+ def is_info(self) -> bool:
359
+ """::
360
+
361
+ boolean = res.is_info
362
+
363
+ Check if this response has a ``1xx`` status :attr:`code`.
364
+ """
365
+ return self._status_class(100)
366
+
367
+ @property
368
+ def is_redirect(self) -> bool:
369
+ """::
370
+
371
+ boolean = res.is_redirect
372
+
373
+ Check if this response has a ``3xx`` status :attr:`code`.
374
+ """
375
+ return self._status_class(300)
376
+
377
+ @property
378
+ def is_server_error(self) -> bool:
379
+ """::
380
+
381
+ boolean = res.is_server_error
382
+
383
+ Check if this response has a ``5xx`` status :attr:`code`.
384
+ """
385
+ return self._status_class(500)
386
+
387
+ @property
388
+ def is_success(self) -> bool:
389
+ """::
390
+
391
+ boolean = res.is_success
392
+
393
+ Check if this response has a ``2xx`` status :attr:`code`.
394
+ """
395
+ return self._status_class(200)
396
+
397
+ @property
398
+ def start_line_size(self) -> int:
399
+ """::
400
+
401
+ size = res.start_line_size
402
+
403
+ Size of the status-line in bytes. Note that this method finalizes the
404
+ response.
405
+ """
406
+ return len(self._start_line())
407
+
408
+ # Not in Mojolicious: a response of urllib
409
+ def to_urllib(self, *, method: str | None = None, url: str | None = None) -> http.client.HTTPResponse:
410
+ """::
411
+
412
+ response = res.to_urllib()
413
+ response = res.to_urllib(method='HEAD', url='http://127.0.0.1/')
414
+
415
+ Turn response into an :class:`http.client.HTTPResponse`, as
416
+ :func:`urllib.request.urlopen` returns for HTTP, parsed by
417
+ :mod:`http.client` from :meth:`~Pyjo.Message.Pyjo_Message.to_bytes`.
418
+ The method of the request tells whether the response has a body, and
419
+ the URL is what :meth:`~http.client.HTTPResponse.geturl` returns. ::
420
+
421
+ # A response for the code that uses urllib
422
+ response = Pyjo.Message.Response.new(code=200).set(body=b'Hi!').to_urllib()
423
+ print(response.read())
424
+ """
425
+ response = http.client.HTTPResponse(cast(Any, _Socket(self.to_bytes())), method=method)
426
+ response.begin()
427
+
428
+ # The URL, as urlopen sets it
429
+ if url is not None:
430
+ response.url = url
431
+ return response
432
+
433
+ # Not in Mojolicious: a response of urllib
434
+ def _from_urllib(self, response: http.client.HTTPResponse | urllib.response.addinfourl) -> None:
435
+ # A response without a status, as of a file: or data: URL, is a success
436
+ self.code = response.status or 200
437
+ self.message = getattr(response, 'reason', None) or None
438
+ self.version = {9: '0.9', 10: '1.0'}.get(getattr(response, 'version', 11), '1.1')
439
+ headers = self.headers
440
+ for name, value in response.headers.items():
441
+ headers.add(name, value)
442
+ body = response.read()
443
+ response.close()
444
+
445
+ # The body without the transfer encoding
446
+ if headers.transfer_encoding:
447
+ headers.remove('Transfer-Encoding')
448
+ headers.content_length = len(body)
449
+ self.body = body
450
+
451
+ def _start_line(self) -> bytes:
452
+ if self._start_buffer is not None:
453
+ return self._start_buffer
454
+ code = self.code or 404
455
+ message = self.message or self.default_message()
456
+ self._start_buffer = f'HTTP/{self.version} {code} {message}\x0d\x0a'.encode('latin-1')
457
+ return self._start_buffer
458
+
459
+ def _status_class(self, *classes: int) -> bool:
460
+ if not (code := self.code):
461
+ return False
462
+ return any(code >= c and code < c + 100 for c in classes)
463
+
464
+
465
+ # Not in Mojolicious: a socket that http.client reads a response from
466
+ class _Socket:
467
+ def __init__(self, data: bytes) -> None:
468
+ self._data = data
469
+
470
+ def makefile(self, mode: str, *args: Any, **kwargs: Any) -> io.BytesIO:
471
+ return io.BytesIO(self._data)
472
+
473
+
474
+ new = Pyjo_Message_Response.new
475
+ object = Pyjo_Message_Response
476
+ Response = Pyjo_Message_Response