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/Headers.py ADDED
@@ -0,0 +1,760 @@
1
+ r"""
2
+ Pyjo.Headers - HTTP headers
3
+ ===========================
4
+ ::
5
+
6
+ from Pyjo.Headers import Headers
7
+
8
+ # Parse
9
+ headers = Headers()
10
+ headers.parse('Content-Length: 42\x0d\x0a')
11
+ headers.parse('Content-Type: text/html\x0d\x0a\x0d\x0a')
12
+ print(headers.content_length)
13
+ print(headers.content_type)
14
+
15
+ # Or import the module, as in Google Python Style Guide
16
+
17
+ import Pyjo.Headers
18
+
19
+ # Parse
20
+ headers = Pyjo.Headers.new()
21
+ headers.parse('Content-Length: 42\x0d\x0a')
22
+ headers.parse('Content-Type: text/html\x0d\x0a\x0d\x0a')
23
+ print(headers.content_length)
24
+ print(headers.content_type)
25
+
26
+ # Build
27
+ headers = Pyjo.Headers.new()
28
+ headers.content_length = 42
29
+ headers.content_type = 'text/plain'
30
+ print(headers.to_str())
31
+
32
+ :mod:`Pyjo.Headers` is a container for HTTP headers, based on :rfc:`7230` and
33
+ :rfc:`7231`.
34
+
35
+ The values are strings; bytes from the wire are decoded as ISO-8859-1, as
36
+ :mod:`http.client` does.
37
+
38
+ The headers also work with the Python standard library: they take an
39
+ :class:`email.message.Message`, such as :class:`http.client.HTTPMessage`, a
40
+ ``dict`` or a list of pairs, they are a mapping of the names to the values
41
+ without regard to case, and they turn into an :class:`email.message.Message`
42
+ with :meth:`to_message` or into a list of pairs with :meth:`to_list`. ::
43
+
44
+ # "text/html"
45
+ Pyjo.Headers.new({'Content-Type': 'text/html'})['content-type']
46
+
47
+ # [('Vary', 'Accept'), ('Vary', 'Accept-Encoding')]
48
+ Pyjo.Headers.new([('Vary', 'Accept'), ('Vary', 'Accept-Encoding')]).to_list()
49
+
50
+ See also
51
+ --------
52
+
53
+ :mod:`Pyjoyment`, https://mojolicious.org.
54
+
55
+ Ported from `Mojo::Headers <https://github.com/mojolicious/mojo/blob/v9.52/lib/Mojo/Headers.pm>`_
56
+ of Mojolicious 9.52.
57
+
58
+ Classes
59
+ -------
60
+ """
61
+
62
+ import Pyjo.Base
63
+ import Pyjo.String.Mixin
64
+
65
+ from Pyjo.Util import header_params
66
+
67
+ import builtins
68
+ import os
69
+ import re
70
+
71
+ from collections.abc import Iterator
72
+ from email.message import Message
73
+ from typing import Self, cast, overload
74
+
75
+
76
+ # The module ends with `object = Pyjo_Headers`, which an annotation of
77
+ # `object` would mean, as annotations are evaluated lazily
78
+ type Anything = builtins.object
79
+
80
+ # Common headers
81
+ NAMES = {
82
+ name.lower(): name
83
+ for name in (
84
+ 'Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges Access-Control-Allow-Origin Allow '
85
+ 'Authorization Cache-Control Connection Content-Disposition Content-Encoding Content-Language Content-Length '
86
+ 'Content-Location Content-Range Content-Security-Policy Content-Type Cookie DNT Date ETag Expect Expires Host '
87
+ 'If-Modified-Since If-None-Match Last-Modified Link Location Origin Proxy-Authenticate Proxy-Authorization '
88
+ 'Range Sec-WebSocket-Accept Sec-WebSocket-Extensions Sec-WebSocket-Key Sec-WebSocket-Protocol '
89
+ 'Sec-WebSocket-Version Server Server-Timing Set-Cookie Status Strict-Transport-Security TE Trailer '
90
+ 'Transfer-Encoding Upgrade User-Agent Vary WWW-Authenticate'
91
+ ).split()
92
+ }
93
+
94
+ # Hop-by-hop headers
95
+ HOP_BY_HOP = [
96
+ name.lower()
97
+ for name in (
98
+ 'Connection',
99
+ 'Keep-Alive',
100
+ 'Proxy-Authenticate',
101
+ 'Proxy-Authorization',
102
+ 'TE',
103
+ 'Trailer',
104
+ 'Transfer-Encoding',
105
+ 'Upgrade',
106
+ )
107
+ ]
108
+
109
+
110
+ class _Shortcut:
111
+ # A property for a common header, as Mojo makes them with monkey_patch
112
+ def __init__(self, name: str, example: str, about: str = '') -> None:
113
+ attr = name.lower().replace('-', '_')
114
+ self.name = name
115
+ self.__doc__ = f"""::
116
+
117
+ {attr} = headers.{attr}
118
+ headers.{attr} = {example}
119
+
120
+ Get or replace current header value, shortcut for the ``{name}`` header{about}.
121
+ """
122
+
123
+ @overload
124
+ def __get__(self, headers: None, owner: type | None = None) -> Self: ...
125
+
126
+ @overload
127
+ def __get__(self, headers: 'Pyjo_Headers', owner: type | None = None) -> str | None: ...
128
+
129
+ def __get__(self, headers: 'Pyjo_Headers | None', owner: type | None = None) -> Self | str | None:
130
+ return self if headers is None else headers.header(self.name)
131
+
132
+ def __set__(self, headers: 'Pyjo_Headers', value: Anything) -> None:
133
+ # Unlike Mojolicious: the value is checked for invalid characters, as add
134
+ # does; Mojo sets it directly
135
+ headers.header(self.name, value)
136
+
137
+
138
+ class Pyjo_Headers(Pyjo.Base.object, Pyjo.String.Mixin.object):
139
+ """
140
+ :mod:`Pyjo.Headers` inherits all methods from :mod:`Pyjo.Base` and
141
+ :mod:`Pyjo.String.Mixin` and implements the following new ones.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ headers: 'Message | dict[str, Anything] | list[tuple[str, Anything]] | Pyjo_Headers | None' = None,
147
+ *,
148
+ max_line_size: int | None = None,
149
+ max_lines: int | None = None,
150
+ ) -> None:
151
+ """::
152
+
153
+ headers = Pyjo.Headers.new()
154
+ headers = Pyjo.Headers.new({'Content-Type': 'text/html'})
155
+ headers = Pyjo.Headers.new([('Vary', 'Accept'), ('Vary', 'Accept-Encoding')])
156
+ headers = Pyjo.Headers.new(http_response.headers)
157
+
158
+ Construct a new :mod:`Pyjo.Headers` object, from headers of the standard
159
+ library if they are given: an :class:`email.message.Message`, such as
160
+ :class:`http.client.HTTPMessage`, a ``dict``, as :meth:`from_dict` takes,
161
+ or a list of pairs.
162
+ """
163
+ self._max_line_size: int | None = max_line_size or None
164
+ self._max_lines: int | None = max_lines or None
165
+
166
+ self._headers: dict[str, list[str]] = {}
167
+ self._names: dict[str, str] = {}
168
+ self._buffer = bytearray()
169
+ self._cache: list[list[str]] = []
170
+ self._state: str | None = None
171
+ self._limit = False
172
+
173
+ # Not in Mojolicious: headers of the standard library
174
+ if isinstance(headers, Message | Pyjo_Headers | list):
175
+ pairs = (
176
+ headers.items()
177
+ if isinstance(headers, Message)
178
+ else headers.to_list()
179
+ if isinstance(headers, Pyjo_Headers)
180
+ else headers
181
+ )
182
+ for name, value in pairs:
183
+ self.add(name, value)
184
+ elif isinstance(headers, dict):
185
+ self.from_dict(headers)
186
+
187
+ @property
188
+ def max_line_size(self) -> int:
189
+ """::
190
+
191
+ size = headers.max_line_size
192
+ headers.max_line_size = 1024
193
+
194
+ Maximum header line size in bytes, defaults to the value of the
195
+ ``PYJO_MAX_LINE_SIZE`` environment variable or ``65536`` (64KiB).
196
+ """
197
+ if self._max_line_size is None:
198
+ # Unlike Mojolicious: 64KiB, as _MAXLINE of http.client, where Mojo has 8KiB, as a header of a response, as
199
+ # Content-Security-Policy, can be longer
200
+ self._max_line_size = int(os.environ.get('PYJO_MAX_LINE_SIZE') or 65536)
201
+ return self._max_line_size
202
+
203
+ @max_line_size.setter
204
+ def max_line_size(self, value: int) -> None:
205
+ self._max_line_size = value
206
+
207
+ @property
208
+ def max_lines(self) -> int:
209
+ """::
210
+
211
+ num = headers.max_lines
212
+ headers.max_lines = 200
213
+
214
+ Maximum number of header lines, defaults to the value of the
215
+ ``PYJO_MAX_LINES`` environment variable or ``100``.
216
+ """
217
+ if self._max_lines is None:
218
+ self._max_lines = int(os.environ.get('PYJO_MAX_LINES') or 100)
219
+ return self._max_lines
220
+
221
+ @max_lines.setter
222
+ def max_lines(self, value: int) -> None:
223
+ self._max_lines = value
224
+
225
+ def __bool__(self) -> bool:
226
+ """::
227
+
228
+ boolean = bool(headers)
229
+
230
+ Always true, also without headers.
231
+ """
232
+ return True
233
+
234
+ # Not in Mojolicious: the mapping protocol of the standard library
235
+ def __contains__(self, name: Anything) -> bool:
236
+ """::
237
+
238
+ boolean = 'Content-Type' in headers
239
+
240
+ Check if a header exists, without regard to case.
241
+ """
242
+ return isinstance(name, str) and name.lower() in self._headers
243
+
244
+ # Not in Mojolicious: the mapping protocol of the standard library
245
+ def __getitem__(self, name: str) -> str:
246
+ """::
247
+
248
+ value = headers['Content-Type']
249
+
250
+ Same as :meth:`header`, but raises :class:`KeyError` for a header that does
251
+ not exist.
252
+ """
253
+ if (value := self.header(name)) is None:
254
+ raise KeyError(name)
255
+ return value
256
+
257
+ # Not in Mojolicious: the mapping protocol of the standard library
258
+ def __iter__(self) -> Iterator[str]:
259
+ """::
260
+
261
+ names = list(headers)
262
+
263
+ Iterate over :meth:`names`.
264
+ """
265
+ return iter(self.names())
266
+
267
+ # Not in Mojolicious: the mapping protocol of the standard library
268
+ def __len__(self) -> int:
269
+ """::
270
+
271
+ size = len(headers)
272
+
273
+ Number of headers, with the values of a header counting once.
274
+ """
275
+ return len(self._headers)
276
+
277
+ def __str__(self) -> str:
278
+ """::
279
+
280
+ string = str(headers)
281
+
282
+ Alias for :meth:`to_str`.
283
+ """
284
+ return self.to_str()
285
+
286
+ accept = _Shortcut('Accept', "'*/*'")
287
+ accept_charset = _Shortcut('Accept-Charset', "'UTF-8'")
288
+ accept_encoding = _Shortcut('Accept-Encoding', "'gzip'")
289
+ accept_language = _Shortcut('Accept-Language', "'de, en'")
290
+ accept_ranges = _Shortcut('Accept-Ranges', "'bytes'")
291
+ access_control_allow_origin = _Shortcut(
292
+ 'Access-Control-Allow-Origin',
293
+ "'*'",
294
+ ' from `Cross-Origin Resource Sharing <https://www.w3.org/TR/cors/>`_',
295
+ )
296
+
297
+ def add(self, name: str, *values: Anything) -> Self:
298
+ """::
299
+
300
+ headers = headers.add('Foo', 'one value')
301
+ headers = headers.add('Foo', 'first value', 'second value')
302
+
303
+ Add header with one or more lines. ::
304
+
305
+ # "Vary: Accept
306
+ # Vary: Accept-Encoding"
307
+ headers.add('Vary', 'Accept').add('Vary', 'Accept-Encoding').to_str()
308
+ """
309
+ # Not in Mojolicious: a list adds its values
310
+ strings = [
311
+ str(v)
312
+ for value in values
313
+ for v in (cast(list[Anything], value) if isinstance(value, (list, tuple)) else [value])
314
+ ]
315
+ for value in strings:
316
+ if '\x0d' in value or '\x0a' in value:
317
+ raise ValueError(f'Invalid characters in {name} header')
318
+
319
+ # Make sure we have a normal case entry for name
320
+ key = name.lower()
321
+ if key not in NAMES:
322
+ self._names.setdefault(key, name)
323
+ self._headers.setdefault(key, []).extend(strings)
324
+
325
+ return self
326
+
327
+ allow = _Shortcut('Allow', "'GET, POST'")
328
+
329
+ def append(self, name: str, value: Anything) -> Self:
330
+ """::
331
+
332
+ headers = headers.append('Vary', 'Accept-Encoding')
333
+
334
+ Append value to header and flatten it if necessary. ::
335
+
336
+ # "Vary: Accept"
337
+ headers.append('Vary', 'Accept').to_str()
338
+
339
+ # "Vary: Accept, Accept-Encoding"
340
+ headers.set(vary='Accept').append('Vary', 'Accept-Encoding').to_str()
341
+ """
342
+ old = self.header(name)
343
+ return self.header(name, f'{old}, {value}' if old is not None else value)
344
+
345
+ authorization = _Shortcut('Authorization', "'Basic Zm9vOmJhcg=='")
346
+ cache_control = _Shortcut('Cache-Control', "'max-age=1, no-cache'")
347
+
348
+ def clone(self) -> Self:
349
+ """::
350
+
351
+ clone = headers.clone()
352
+
353
+ Return a new :mod:`Pyjo.Headers` object cloned from these headers.
354
+ """
355
+ clone = self.new()
356
+ clone._names = dict(self._names)
357
+ clone._headers = {key: list(values) for key, values in self._headers.items()}
358
+ return clone
359
+
360
+ connection = _Shortcut('Connection', "'close'")
361
+ content_disposition = _Shortcut('Content-Disposition', "'foo'")
362
+ content_encoding = _Shortcut('Content-Encoding', "'gzip'")
363
+ content_language = _Shortcut('Content-Language', "'en'")
364
+ content_length = _Shortcut('Content-Length', '4000')
365
+ content_location = _Shortcut('Content-Location', "'http://127.0.0.1/foo'")
366
+ content_range = _Shortcut('Content-Range', "'bytes 2-8/100'")
367
+ content_security_policy = _Shortcut(
368
+ 'Content-Security-Policy',
369
+ "'default-src https:'",
370
+ ' from `Content Security Policy 1.0 <https://www.w3.org/TR/CSP/>`_',
371
+ )
372
+ content_type = _Shortcut('Content-Type', "'text/plain'")
373
+ cookie = _Shortcut('Cookie', "'f=b'", ' from :rfc:`6265`')
374
+ date = _Shortcut('Date', "'Sun, 17 Aug 2008 16:27:35 GMT'")
375
+
376
+ def dehop(self) -> Self:
377
+ """::
378
+
379
+ headers = headers.dehop()
380
+
381
+ Remove hop-by-hop headers that should not be retransmitted.
382
+ """
383
+ for key in HOP_BY_HOP:
384
+ self._headers.pop(key, None)
385
+ return self
386
+
387
+ dnt = _Shortcut(
388
+ 'DNT',
389
+ '1',
390
+ ' (Do Not Track), which has no specification yet, but is very commonly used',
391
+ )
392
+ etag = _Shortcut('ETag', '\'"abc321"\'')
393
+
394
+ def every_header(self, name: str) -> list[str]:
395
+ """::
396
+
397
+ all = headers.every_header('Location')
398
+
399
+ Similar to :meth:`header`, but returns all headers sharing the same name as a
400
+ list. ::
401
+
402
+ # Get first header value
403
+ print(headers.every_header('Location')[0])
404
+ """
405
+ return self._headers.get(name.lower(), [])
406
+
407
+ expect = _Shortcut('Expect', "'100-continue'")
408
+ expires = _Shortcut('Expires', "'Thu, 01 Dec 1994 16:00:00 GMT'")
409
+
410
+ def from_dict(self, d: dict[str, Anything]) -> Self:
411
+ """::
412
+
413
+ headers = headers.from_dict({'Cookie': 'a=b'})
414
+ headers = headers.from_dict({'Cookie': ['a=b', 'c=d']})
415
+ headers = headers.from_dict({})
416
+
417
+ Parse headers from a ``dict``, an empty one removes all headers.
418
+ """
419
+ # Empty dict deletes all headers
420
+ if not d:
421
+ self._headers = {}
422
+
423
+ # Merge
424
+ for header, value in d.items():
425
+ self.add(header, value)
426
+
427
+ return self
428
+
429
+ # Not in Mojolicious: the mapping protocol of the standard library
430
+ def get(self, name: str, default: str | None = None) -> str | None:
431
+ """::
432
+
433
+ value = headers.get('Content-Type')
434
+ value = headers.get('Content-Type', 'text/plain')
435
+
436
+ Same as :meth:`header`, but returns the default for a header that does not
437
+ exist.
438
+ """
439
+ value = self.header(name)
440
+ return value if value is not None else default
441
+
442
+ @overload
443
+ def header(self, name: str, /) -> str | None: ...
444
+
445
+ @overload
446
+ def header(self, name: str, value: Anything, /, *values: Anything) -> Self: ...
447
+
448
+ def header(self, name: str, /, *values: Anything) -> Self | str | None:
449
+ """::
450
+
451
+ value = headers.header('Foo')
452
+ headers = headers.header('Foo', 'one value')
453
+ headers = headers.header('Foo', 'first value', 'second value')
454
+
455
+ Get or replace the current header values.
456
+ """
457
+ # Replace
458
+ if values:
459
+ return self.remove(name).add(name, *values)
460
+
461
+ if (headers := self._headers.get(name.lower())) is None:
462
+ return None
463
+ return ', '.join(headers)
464
+
465
+ host = _Shortcut('Host', "'127.0.0.1'")
466
+ if_modified_since = _Shortcut('If-Modified-Since', "'Sun, 17 Aug 2008 16:27:35 GMT'")
467
+ if_none_match = _Shortcut('If-None-Match', '\'"abc321"\'')
468
+
469
+ @property
470
+ def is_finished(self) -> bool:
471
+ """::
472
+
473
+ boolean = headers.is_finished
474
+
475
+ Check if header parser is finished.
476
+ """
477
+ return self._state == 'finished'
478
+
479
+ @property
480
+ def is_limit_exceeded(self) -> bool:
481
+ """::
482
+
483
+ boolean = headers.is_limit_exceeded
484
+
485
+ Check if headers have exceeded :attr:`max_line_size` or :attr:`max_lines`.
486
+ """
487
+ return self._limit
488
+
489
+ # Not in Mojolicious: the mapping protocol of the standard library
490
+ def items(self) -> list[tuple[str, str]]:
491
+ """::
492
+
493
+ items = headers.items()
494
+
495
+ Return a list of :meth:`names` with the values that :meth:`header` gives.
496
+ """
497
+ return [(name, ', '.join(self._headers[name.lower()])) for name in self.names()]
498
+
499
+ # Not in Mojolicious: the mapping protocol of the standard library
500
+ def keys(self) -> list[str]:
501
+ """::
502
+
503
+ names = headers.keys()
504
+
505
+ Alias for :meth:`names`.
506
+ """
507
+ return self.names()
508
+
509
+ last_modified = _Shortcut('Last-Modified', "'Sun, 17 Aug 2008 16:27:35 GMT'")
510
+
511
+ def leftovers(self) -> bytes:
512
+ """::
513
+
514
+ data = headers.leftovers()
515
+
516
+ Get and remove leftover data from header parser.
517
+ """
518
+ buffer, self._buffer = bytes(self._buffer), bytearray()
519
+ return buffer
520
+
521
+ link = _Shortcut('Link', '\'<http://127.0.0.1/foo/3>; rel="next"\'', ' from :rfc:`5988`')
522
+
523
+ @property
524
+ def links(self) -> dict[str, dict[str, str]]:
525
+ """::
526
+
527
+ links = headers.links
528
+ headers.links = {'next': 'http://example.com/foo', 'prev': 'http://example.com/bar'}
529
+
530
+ Get or set web links from or to ``Link`` header according to :rfc:`5988`. ::
531
+
532
+ # Extract information about next page
533
+ print(headers.links['next']['link'])
534
+ print(headers.links['next']['title'])
535
+ """
536
+ header = self.link or ''
537
+ data: dict[str, dict[str, str]] = {}
538
+ while m := re.match(r'[,\s]*<(.+?)>', header):
539
+ target = m[1]
540
+ params, header = header_params(header[m.end() :])
541
+ if (rel := params.get('rel')) is not None:
542
+ data.setdefault(rel, {**params, 'link': target})
543
+
544
+ return data
545
+
546
+ @links.setter
547
+ def links(self, links: dict[str, str]) -> None:
548
+ self.link = ', '.join(f'<{links[rel]}>; rel="{rel}"' for rel in sorted(links))
549
+
550
+ location = _Shortcut('Location', "'http://127.0.0.1/foo'")
551
+
552
+ def names(self) -> list[str]:
553
+ """::
554
+
555
+ names = headers.names()
556
+
557
+ Return a list with all currently defined headers. ::
558
+
559
+ # Names of all headers
560
+ for name in headers.names():
561
+ print(name)
562
+ """
563
+ return [NAMES.get(key) or self._names[key] for key in sorted(self._headers)]
564
+
565
+ origin = _Shortcut('Origin', "'http://example.com'", ' from :rfc:`6454`')
566
+
567
+ def parse(self, chunk: str | bytes | bytearray) -> Self:
568
+ r"""::
569
+
570
+ headers = headers.parse('Content-Type: text/plain\x0d\x0a\x0d\x0a')
571
+
572
+ Parse formatted headers, given as characters or as bytes.
573
+ """
574
+ self._state = 'headers'
575
+ # Not in Mojolicious: characters, as ISO-8859-1
576
+ self._buffer += chunk if isinstance(chunk, (bytes, bytearray)) else chunk.encode('latin-1')
577
+ headers = self._cache
578
+ size = self.max_line_size
579
+ lines = self.max_lines
580
+ while (length := self._buffer.find(b'\x0a') + 1) > 0:
581
+ raw = self._buffer[: length - 1]
582
+ del self._buffer[:length]
583
+ line = (raw[:-1] if raw.endswith(b'\x0d') else raw).decode('latin-1')
584
+
585
+ # Check line size limit
586
+ if length > size or len(headers) >= lines:
587
+ self._state, self._limit = 'finished', True
588
+ return self
589
+
590
+ # New header
591
+ if header := re.match(r'^(\S[^:]*):\s*(.*)$', line):
592
+ headers.append([header[1], header[2]])
593
+
594
+ # Multi-line
595
+ elif (space := re.match(r'\s+', line)) and headers:
596
+ headers[-1][1] += ' ' + line[space.end() :]
597
+
598
+ # Empty line
599
+ else:
600
+ for name, value in headers:
601
+ self.add(name, value)
602
+ self._state, self._cache = 'finished', []
603
+ return self
604
+
605
+ # Check line size limit
606
+ if len(self._buffer) > size:
607
+ self._state, self._limit = 'finished', True
608
+
609
+ return self
610
+
611
+ proxy_authenticate = _Shortcut('Proxy-Authenticate', '\'Basic "realm"\'')
612
+ proxy_authorization = _Shortcut('Proxy-Authorization', "'Basic Zm9vOmJhcg=='")
613
+ range = _Shortcut('Range', "'bytes=2-8'")
614
+
615
+ @property
616
+ def referer(self) -> str | None:
617
+ """::
618
+
619
+ referrer = headers.referer
620
+ headers.referer = 'http://example.com'
621
+
622
+ Alias for :attr:`referrer`.
623
+ """
624
+ return self.referrer
625
+
626
+ @referer.setter
627
+ def referer(self, value: Anything) -> None:
628
+ self.referrer = value
629
+
630
+ @property
631
+ def referrer(self) -> str | None:
632
+ """::
633
+
634
+ referrer = headers.referrer
635
+ headers.referrer = 'http://example.com'
636
+
637
+ Get or replace current header value, shortcut for the ``Referer`` header,
638
+ there was a typo in :rfc:`2068` which resulted in ``Referer`` becoming an
639
+ official header.
640
+ """
641
+ return self.header('Referer')
642
+
643
+ @referrer.setter
644
+ def referrer(self, value: Anything) -> None:
645
+ self.header('Referer', value)
646
+
647
+ def remove(self, name: str) -> Self:
648
+ """::
649
+
650
+ headers = headers.remove('Foo')
651
+
652
+ Remove a header.
653
+ """
654
+ self._headers.pop(name.lower(), None)
655
+ return self
656
+
657
+ sec_websocket_accept = _Shortcut(
658
+ 'Sec-WebSocket-Accept',
659
+ "'s3pPLMBiTxaQ9kYGzzhZRbK+xOo='",
660
+ ' from :rfc:`6455`',
661
+ )
662
+ sec_websocket_extensions = _Shortcut('Sec-WebSocket-Extensions', "'foo'", ' from :rfc:`6455`')
663
+ sec_websocket_key = _Shortcut('Sec-WebSocket-Key', "'dGhlIHNhbXBsZSBub25jZQ=='", ' from :rfc:`6455`')
664
+ sec_websocket_protocol = _Shortcut('Sec-WebSocket-Protocol', "'sample'", ' from :rfc:`6455`')
665
+ sec_websocket_version = _Shortcut('Sec-WebSocket-Version', '13', ' from :rfc:`6455`')
666
+ server = _Shortcut('Server', "'Pyjoyment'")
667
+ server_timing = _Shortcut(
668
+ 'Server-Timing',
669
+ "'app;desc=Pyjoyment;dur=0.0001'",
670
+ ' from `Server Timing <https://www.w3.org/TR/server-timing/>`_',
671
+ )
672
+ set_cookie = _Shortcut('Set-Cookie', "'f=b; path=/'", ' from :rfc:`6265`')
673
+ status = _Shortcut('Status', "'200 OK'", ' from :rfc:`3875`')
674
+ strict_transport_security = _Shortcut('Strict-Transport-Security', "'max-age=31536000'", ' from :rfc:`6797`')
675
+ te = _Shortcut('TE', "'chunked'")
676
+
677
+ # Not in Mojolicious: bytes for the standard library and the network
678
+ def to_bytes(self) -> bytes:
679
+ """::
680
+
681
+ data = headers.to_bytes()
682
+
683
+ Turn headers into bytes, suitable for HTTP messages, with the characters
684
+ encoded as ISO-8859-1.
685
+ """
686
+ return self.to_str().encode('latin-1')
687
+
688
+ def to_dict(self, multi: bool = False) -> dict[str, str] | dict[str, list[str]]:
689
+ """::
690
+
691
+ single = headers.to_dict()
692
+ multi = headers.to_dict(True)
693
+
694
+ Turn headers into ``dict``, lists to represent multiple headers with the same
695
+ name are disabled by default. ::
696
+
697
+ print(headers.to_dict()['DNT'])
698
+ """
699
+ if multi:
700
+ return {name: list(self._headers[name.lower()]) for name in self.names()}
701
+ return dict(self.items())
702
+
703
+ # Not in Mojolicious: a list of pairs for the standard library
704
+ def to_list(self) -> list[tuple[str, str]]:
705
+ """::
706
+
707
+ pairs = headers.to_list()
708
+
709
+ Turn headers into a list of pairs, one for each value, as WSGI and
710
+ :meth:`http.client.HTTPResponse.getheaders` have them.
711
+ """
712
+ return [(name, value) for name in self.names() for value in self._headers[name.lower()]]
713
+
714
+ # Not in Mojolicious: email.message of the standard library
715
+ def to_message(self) -> Message:
716
+ """::
717
+
718
+ message = headers.to_message()
719
+
720
+ Turn headers into an :class:`email.message.Message`, with each value as a
721
+ header of its own, as :mod:`http.client` has them.
722
+ """
723
+ message = Message()
724
+ for name, value in self.to_list():
725
+ message[name] = value
726
+ return message
727
+
728
+ def to_str(self) -> str:
729
+ """::
730
+
731
+ string = headers.to_str()
732
+
733
+ Turn headers into a string, suitable for HTTP messages.
734
+ """
735
+ # Make sure multi-line values are formatted correctly
736
+ return '\x0d\x0a'.join(f'{name}: {value}' for name, value in self.to_list())
737
+
738
+ trailer = _Shortcut('Trailer', "'X-Foo'")
739
+ transfer_encoding = _Shortcut('Transfer-Encoding', "'chunked'")
740
+ upgrade = _Shortcut('Upgrade', "'websocket'")
741
+ user_agent = _Shortcut('User-Agent', "'Pyjoyment/1.0'")
742
+
743
+ # Not in Mojolicious: the mapping protocol of the standard library
744
+ def values(self) -> list[str]:
745
+ """::
746
+
747
+ values = headers.values()
748
+
749
+ Return a list of the values that :meth:`header` gives, in the order of
750
+ :meth:`names`.
751
+ """
752
+ return [value for _, value in self.items()]
753
+
754
+ vary = _Shortcut('Vary', "'*'")
755
+ www_authenticate = _Shortcut('WWW-Authenticate', '\'Basic realm="realm"\'')
756
+
757
+
758
+ new = Pyjo_Headers.new
759
+ object = Pyjo_Headers
760
+ Headers = Pyjo_Headers